diff --git a/.babel-plugin-macrosrc.js b/.babel-plugin-macrosrc.js deleted file mode 100644 index 4bcdbb88b7..0000000000 --- a/.babel-plugin-macrosrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - twin: { - preset: 'styled-components', - autoCssProp: true, - }, - styledComponents: { - pure: true, - displayName: false, - fileName: false, - }, -}; diff --git a/.editorconfig b/.editorconfig index 1ba6cf9f70..a041471162 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,15 +1,17 @@ root = true [*] -end_of_line = lf -insert_final_newline = true indent_style = space indent_size = 4 +tab_width = 4 +end_of_line = lf charset = utf-8 trim_trailing_whitespace = true - -[.*yml] -indent_size = 2 +insert_final_newline = true [*.md] trim_trailing_whitespace = false + +[*.{md,nix,yml,yaml}] +indent_size = 2 +tab_width = 2 diff --git a/.env.ci b/.env.ci index e99f46691b..1a9e848e3b 100644 --- a/.env.ci +++ b/.env.ci @@ -2,13 +2,15 @@ APP_ENV=testing APP_DEBUG=true APP_KEY=SomeRandomString3232RandomString APP_THEME=pterodactyl -APP_TIMEZONE=America/Los_Angeles +APP_TIMEZONE=UTC APP_URL=http://localhost/ +APP_ENVIRONMENT_ONLY=true -TESTING_DB_HOST=127.0.0.1 -TESTING_DB_DATABASE=panel_test -TESTING_DB_USERNAME=root -TESTING_DB_PASSWORD= +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_DATABASE=testing +DB_USERNAME=root +DB_PASSWORD= CACHE_DRIVER=array SESSION_DRIVER=array @@ -16,4 +18,3 @@ MAIL_DRIVER=array QUEUE_DRIVER=sync HASHIDS_SALT=test123 -APP_ENVIRONMENT_ONLY=true diff --git a/.env.dusk b/.env.dusk deleted file mode 100644 index 237f61d323..0000000000 --- a/.env.dusk +++ /dev/null @@ -1,26 +0,0 @@ -APP_ENV=local -APP_DEBUG=false -APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i -APP_JWT_KEY=test1234 -APP_TIMEZONE=America/Los_Angeles -APP_URL=http://pterodactyl.local - -CACHE_DRIVER=file -SESSION_DRIVER=file - -HASHIDS_SALT=IqRr0g82tCTeuyxGs8RV -HASHIDS_LENGTH=8 - -MAIL_DRIVER=log -MAIL_FROM=support@pterodactyl.io -QUEUE_DRIVER=array - -APP_SERVICE_AUTHOR=testing@pterodactyl.io -MAIL_FROM_NAME="Pterodactyl Panel" -RECAPTCHA_ENABLED=false - -DB_CONNECTION=testing -TESTING_DB_HOST=192.168.1.202 -TESTING_DB_DATABASE=panel_test -TESTING_DB_USERNAME=panel_test -TESTING_DB_PASSWORD=Test1234 diff --git a/.env.example b/.env.example index e2e9d2dce4..fc97db9590 100644 --- a/.env.example +++ b/.env.example @@ -2,37 +2,43 @@ APP_ENV=production APP_DEBUG=false APP_KEY= APP_THEME=pterodactyl -APP_TIMEZONE=America/New_York -APP_CLEAR_TASKLOG=720 -APP_DELETE_MINUTES=10 +APP_TIMEZONE=UTC +APP_URL=http://panel.example.com +APP_LOCALE=en APP_ENVIRONMENT_ONLY=true + LOG_CHANNEL=daily -APP_LOCALE=en -APP_URL=http://panel.example.com +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug +DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=panel DB_USERNAME=pterodactyl DB_PASSWORD= +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +CACHE_DRIVER=file +QUEUE_CONNECTION=redis +SESSION_DRIVER=file + HASHIDS_SALT= HASHIDS_LENGTH=8 -MAIL_DRIVER=smtp +MAIL_MAILER=smtp MAIL_HOST=smtp.example.com MAIL_PORT=25 MAIL_USERNAME= MAIL_PASSWORD= MAIL_ENCRYPTION=tls -MAIL_FROM=no-reply@example.com -MAILGUN_ENDPOINT=api.mailgun.net +MAIL_FROM_ADDRESS=no-reply@example.com +MAIL_FROM_NAME="Pterodactyl Panel" # You should set this to your domain to prevent it defaulting to 'localhost', causing # mail servers such as Gmail to reject your mail. # # @see: https://github.com/pterodactyl/panel/pull/3110 -# SERVER_NAME=panel.example.com - -QUEUE_HIGH=high -QUEUE_STANDARD=standard -QUEUE_LOW=low +# MAIL_EHLO_DOMAIN=panel.example.com diff --git a/.eslintignore b/.eslintignore index 6fdfb93fea..01050140aa 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,6 @@ public node_modules resources/views +babel.config.js +tailwind.config.js webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..ad11a0bca4 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,52 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + project: './tsconfig.json', + tsconfigRootDir: './', + }, + settings: { + react: { + pragma: 'React', + version: 'detect', + }, + linkComponents: [ + { name: 'Link', linkAttribute: 'to' }, + { name: 'NavLink', linkAttribute: 'to' }, + ], + }, + env: { + browser: true, + es6: true, + }, + plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint'], + extends: [ + // 'standard', + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jest-dom/recommended', + ], + rules: { + eqeqeq: 'error', + 'prettier/prettier': ['error', {}, { usePrettierrc: true }], + // TypeScript can infer this significantly better than eslint ever can. + 'react/prop-types': 0, + 'react/display-name': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-non-null-assertion': 0, + // This setup is required to avoid a spam of errors when running eslint about React being + // used before it is defined. + // + // @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use + 'no-use-before-define': 0, + '@typescript-eslint/no-use-before-define': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }], + 'react/no-unknown-property': ['error', { ignore: ['css'] }], + }, +}; diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 1a68b1dc8b..0000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,98 +0,0 @@ -parser: "@typescript-eslint/parser" -parserOptions: - ecmaVersion: 6 - ecmaFeatures: - jsx: true - project: "./tsconfig.json" - tsconfigRootDir: "./" -settings: - react: - pragma: "React" - version: "detect" - linkComponents: - - name: Link - linkAttribute: to - - name: NavLink - linkAttribute: to -env: - browser: true - es6: true -plugins: - - "react" - - "react-hooks" - - "@typescript-eslint" -extends: - - "standard" - - "plugin:react/recommended" - - "plugin:@typescript-eslint/recommended" -rules: - quotes: - - warn - - single - indent: - - warn - - 4 - - SwitchCase: 1 - semi: - - warn - - always - comma-dangle: - - warn - - always-multiline - spaced-comment: - - warn - array-bracket-spacing: - - warn - - always - # Remove errors for not having newlines between operands of ternary expressions https://eslint.org/docs/rules/multiline-ternary - multiline-ternary: 0 - "react-hooks/rules-of-hooks": - - error - "react-hooks/exhaustive-deps": 0 - "@typescript-eslint/explicit-function-return-type": 0 - "@typescript-eslint/explicit-member-accessibility": 0 - "@typescript-eslint/ban-ts-ignore": 0 - "@typescript-eslint/no-explicit-any": 0 - "@typescript-eslint/no-non-null-assertion": 0 - "@typescript-eslint/ban-ts-comment": 0 - # This would be nice to have, but don't want to deal with the warning spam at the moment. - "@typescript-eslint/explicit-module-boundary-types": 0 - no-restricted-imports: - - error - - paths: - - name: styled-components - message: Please import from styled-components/macro. - patterns: - - "!styled-components/macro" - # Not sure, this rule just doesn't work right and is protected by our use of Typescript anyways - # so I'm just not going to worry about it. - "react/prop-types": 0 - "react/display-name": 0 - "react/jsx-indent-props": - - warn - - 4 - "react/jsx-boolean-value": - - warn - - never - "react/jsx-closing-bracket-location": - - 1 - - "line-aligned" - "react/jsx-closing-tag-location": 1 - # This setup is required to avoid a spam of errors when running eslint about React being - # used before it is defined. - # - # see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use - no-use-before-define: 0 - "@typescript-eslint/no-use-before-define": - - warn -overrides: - - files: - - "**/*.tsx" - rules: - operator-linebreak: - - error - - before - - overrides: - "&&": "after" - "?": "ignore" - ":": "ignore" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 985087d16e..4317a37b2c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ -github: [DaneEveritt] -custom: ["https://paypal.me/PterodactylSoftware"] +github: [pterodactyl] diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.yml b/.github/ISSUE_TEMPLATE/1-bug-report.yml new file mode 100644 index 0000000000..651f582273 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.yml @@ -0,0 +1,82 @@ +name: Bug Reports +description: For reporting known and reproducible issues with the software. +body: + - type: markdown + attributes: + value: | + Bug reports should only be used for reporting issues with how the software works. For assistance installing this software, as well as debugging issues with dependencies, please use our [Discord server](https://discord.gg/pterodactyl). + + - type: textarea + attributes: + label: Current Behavior + description: Please provide a clear & concise description of the issue. + validations: + required: true + + - type: textarea + attributes: + label: Expected Behavior + description: Please describe what you expected to happen. + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will result in this issue being closed. + validations: + required: true + + - type: input + id: panel-version + attributes: + label: Panel Version + description: Version number of your Panel (latest is not a version) + placeholder: 1.4.0 + validations: + required: true + + - type: input + id: wings-version + attributes: + label: Wings Version + description: Version number of your Wings (latest is not a version) + placeholder: 1.4.2 + validations: + required: true + + - type: input + id: egg-details + attributes: + label: Games and/or Eggs Affected + description: Please include the specific game(s) or egg(s) you are running into this bug with. + placeholder: Minecraft (Paper), Minecraft (Forge) + + - type: input + id: docker-image + attributes: + label: Docker Image + description: The specific Docker image you are using for the game(s) above. + placeholder: ghcr.io/pterodactyl/yolks:java_17 + + - type: textarea + id: panel-logs + attributes: + label: Error Logs + description: | + Run the following command to collect logs on your system. + + Wings: `sudo wings diagnostics` + Panel: `tail -n 150 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc pteropaste.com 99` + placeholder: "https://pteropaste.com/a1h6z" + render: bash + validations: + required: false + + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please [search here](https://github.com/pterodactyl/panel/issues) to see if an issue already exists for your problem. + options: + - label: I have searched the existing issues before opening this issue. I understand that maintainers may close this issue without communication if I have not provided sufficient information. + required: true diff --git a/.github/ISSUE_TEMPLATE/2-approved.yml b/.github/ISSUE_TEMPLATE/2-approved.yml new file mode 100644 index 0000000000..0f2b3fc50b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-approved.yml @@ -0,0 +1,18 @@ +name: Pre-Discussed and Approved Topics +description: For topics already previously discussed and approved in the GitHub Discussions section. +body: + - type: markdown + attributes: + value: | + **DO NOT** open a new issue for feature requests _without prior maintainer approval_ and corresponding + thread in the discussions section. You MUST link to the corresponding discussion thread when opening + this feature request issue or it will be closed without further consideration. + - type: textarea + attributes: + label: The Feature Request + - type: input + attributes: + label: GitHub Discussion Link + description: Link to the GitHub Discussions thread where the feature was previously discussed and approved. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index adf3d27383..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Bug Report -description: Something isn't working quite right in the software. -labels: [not confirmed] -body: -- type: markdown - attributes: - value: | - Bug reports should only be used for reporting issues with how the software works. For assistance installing this software, as well as debugging issues with dependencies, please use our [Discord server](https://discord.gg/pterodactyl). - -- type: textarea - attributes: - label: Current Behavior - description: Please provide a clear & concise description of the issue. - validations: - required: true - -- type: textarea - attributes: - label: Expected Behavior - description: Please describe what you expected to happen. - validations: - required: true - -- type: textarea - attributes: - label: Steps to Reproduce - description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will result in this issue being closed. - validations: - required: true - -- type: input - id: panel-version - attributes: - label: Panel Version - description: Version number of your Panel (latest is not a version) - placeholder: 1.4.0 - validations: - required: true - -- type: input - id: wings-version - attributes: - label: Wings Version - description: Version number of your Wings (latest is not a version) - placeholder: 1.4.2 - validations: - required: true - -- type: input - id: egg-details - attributes: - label: Games and/or Eggs Affected - description: Please include the specific game(s) or egg(s) you are running into this bug with. - placeholder: Minecraft (Paper), Minecraft (Forge) - -- type: input - id: docker-image - attributes: - label: Docker Image - description: The specific Docker image you are using for the game(s) above. - placeholder: ghcr.io/pterodactyl/yolks:java_17 - -- type: textarea - id: panel-logs - attributes: - label: Error Logs - description: | - Run the following command to collect logs on your system. - - Wings: `sudo wings diagnostics` - Panel: `tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99` - placeholder: "https://bin.ptdl.co/a1h6z" - render: bash - validations: - required: false - -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please [search here](https://github.com/pterodactyl/panel/issues) to see if an issue already exists for your problem. - options: - - label: I have searched the existing issues before opening this issue. - required: true - - label: I have provided all relevant details, including the specific game and Docker images I am using if this issue is related to running a server. - required: true - - label: I have checked in the Discord server and believe this is a bug with the software, and not a configuration issue with my specific system. - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 09011627c5..6bbf8e071f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Installation Help + - name: Features, Help, or Questions + url: https://github.com/orgs/pterodactyl/discussions/categories/feature-requests + about: The preferred method for getting help with Pterodactyl or seeking new features for the software. + - name: Discord url: https://discord.gg/pterodactyl - about: Please visit our Discord for help with your installation. - - name: General Question - url: https://discord.gg/pterodactyl - about: Please visit our Discord for general questions about Pterodactyl. + about: For quicker support with installations issues or running ideas by the community. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index d782e39832..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Feature Request -description: Suggest a new feature or improvement for the software. -labels: [feature request] -body: -- type: checkboxes - attributes: - label: Is there an existing feature request for this? - description: Please [search here](https://github.com/pterodactyl/panel/issues?q=is%3Aissue) to see if someone else has already suggested this. - options: - - label: I have searched the existing issues before opening this feature request. - required: true - -- type: textarea - attributes: - label: Describe the feature you would like to see. - description: "A clear & concise description of the feature you'd like to have added, and what issues it would solve." - validations: - required: true - -- type: textarea - attributes: - label: Describe the solution you'd like. - description: "You must explain how you'd like to see this feature implemented. Technical implementation details are not necessary, rather an idea of how you'd like to see this feature used." - validations: - required: true - -- type: textarea - attributes: - label: Additional context to this request. - description: "Add any other context or screenshots about the feature request." - validations: - required: false diff --git a/.github/docker/README.md b/.github/docker/README.md index b195b83001..5cf97de442 100644 --- a/.github/docker/README.md +++ b/.github/docker/README.md @@ -27,7 +27,7 @@ docker-compose exec panel php artisan p:user:make ## Environment Variables There are multiple environment variables to configure the panel when not providing your own `.env` file, see the following table for details on each available option. -Note: If your `APP_URL` starts with `https://` you need to provide an `LETSENCRYPT_EMAIL` as well so Certificates can be generated. +Note: If your `APP_URL` starts with `https://` you need to provide an `LE_EMAIL` as well so Certificates can be generated. | Variable | Description | Required | | ------------------- | ------------------------------------------------------------------------------ | -------- | diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index d3df9c1505..858a6fd12e 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -3,7 +3,7 @@ cd /app mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \ && chmod 777 /var/log/panel/logs/ \ - && ln -s /var/log/panel/logs/ /app/storage/logs/ + && ln -s /app/storage/logs/ /var/log/panel/ ## check for .env file and generate app keys if missing if [ -f /app/var/.env ]; then @@ -26,6 +26,17 @@ else echo -e "APP_KEY=$APP_KEY" > /app/var/.env fi + ## generate a random salt for hashids if not provided + if [ -z $HASHIDS_SALT ]; then + echo -e "Generating hashids salt." + HASHIDS_SALT=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9!@#$%^&*()_+?><~' | fold -w 20 | head -n 1) + echo -e "Generated hashids salt: $HASHIDS_SALT" + echo -e "HASHIDS_SALT=$HASHIDS_SALT" >> /app/var/.env + else + echo -e "HASHIDS_SALT exists in environment, using that." + echo -e "HASHIDS_SALT=$HASHIDS_SALT" >> /app/var/.env + fi + ln -s /app/var/.env /app/ fi @@ -60,6 +71,13 @@ if [[ -z $DB_PORT ]]; then DB_PORT=3306 fi +## check log folder permissions +echo "Checking log folder permissions." +if [ "$(stat -c %U:%G /app/storage/logs)" != "nginx" ]; then + echo "Fixing log folder permissions." + chown -R nginx: /app/storage/logs/ +fi + ## check for DB up before starting the panel echo "Checking database status." until nc -z -v -w30 $DB_HOST $DB_PORT diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000000..73e3f74f7d --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,32 @@ +name: Build + +on: + pull_request: + branches: + - "1.0-develop" + +jobs: + ui: + name: UI + runs-on: ubuntu-24.04 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + node-version: [ 22 ] + steps: + - name: Code Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + - run: yarn tsc + - run: yarn lint + - run: yarn build:production diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 71f610ef7d..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Build Panel -on: - push: - branches: - - 'develop' - - 'v2' - pull_request: -jobs: - build: - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" - strategy: - matrix: - node-version: [16.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm install -g yarn - - run: yarn install - - run: yarn build:production diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..5268cf3ae4 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,59 @@ +name: Tests + +on: + pull_request: + branches: + - "1.0-develop" + +jobs: + tests: + name: Tests + runs-on: ubuntu-24.04 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + php: + - 8.2 + - 8.3 + database: + - mariadb:10 + - mariadb:11 + - mysql:8 + - mysql:9 + services: + database: + image: ${{ matrix.database }} + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: testing + ports: + - 3306 + steps: + - uses: actions/checkout@v4 + - id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer-${{ matrix.php }}- + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip + tools: composer:v2 + coverage: none + - run: cp .env.ci .env + - run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + - run: vendor/bin/php-cs-fixer fix --dry-run --diff + - run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit + env: + DB_HOST: UNIT_NO_DB + - run: vendor/bin/phpunit tests/Integration + env: + DB_PORT: ${{ job.services.database.ports[3306] }} + DB_USERNAME: root diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000000..afab3248aa --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,66 @@ +name: Docker + +on: + push: + branches: + - 1.0-develop + release: + types: + - published + +jobs: + push: + name: Push + runs-on: ubuntu-24.04 + if: "!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip')" + permissions: + contents: read + packages: write + steps: + - name: Code checkout + uses: actions/checkout@v4 + + - name: Docker metadata + id: docker_meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/pterodactyl/panel + flavor: | + latest=false + tags: | + type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }} + type=ref,event=tag + type=ref,event=branch + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + if: "github.event_name != 'pull_request'" + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Update version + if: "github.event_name == 'release' && github.event.action == 'published'" + env: + REF: ${{ github.event.release.tag_name }} + run: | + sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php + + - name: Build and Push + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 + labels: ${{ steps.docker_meta.outputs.labels }} + tags: ${{ steps.docker_meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 444208c538..0000000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish Docker Image -on: - push: - branches: - - 'develop' - tags: - - 'v*' -jobs: - push_to_registry: - name: Push Image to GitHub Packages - runs-on: ubuntu-latest - # Always run against a tag, even if the commit into the tag has [docker skip] - # within the commit message. - if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))" - steps: - - uses: actions/checkout@v2 - - uses: crazy-max/ghaction-docker-meta@v1 - id: docker_meta - with: - images: ghcr.io/pterodactyl/panel - - uses: docker/setup-qemu-action@v1 - - uses: docker/setup-buildx-action@v1 - - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.REGISTRY_TOKEN }} - - name: Bump Version - if: "!contains(github.ref, 'develop')" - env: - REF: ${{ github.ref }} - run: | - sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php - - name: Release Production Build - uses: docker/build-push-action@v2 - if: "!contains(github.ref, 'develop')" - with: - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} - - name: Release Development Build - uses: docker/build-push-action@v2 - if: "contains(github.ref, 'develop')" - with: - push: ${{ github.event_name != 'pull_request' }} - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000..708aa22b50 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,49 @@ +name: Release +on: + push: + tags: + - "v*" +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + - run: yarn install --frozen-lockfile + - run: yarn tsc + - run: yarn build:production + - name: create release branch and bump version + env: + VERSION: ${{ github.ref_name }} + run: | + BRANCH=release/${{ env.VERSION }} + git config --local user.email 'ci@pterodactyl.io' + git config --local user.name 'Pterodactyl CI' + git checkout -b "$BRANCH" + git push -u origin "$BRANCH" + sed -i "s/'canary'/'${VERSION:1}'/" config/app.php + git add config/app.php + git commit -m 'ci(release): bump version' + git push + - name: create release archive + run: | + rm -rf node_modules tests CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix + tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json + - name: write changelog + run: | + sed -n "/^## ${{ github.ref_name }}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG + - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + prerelease: ${{ contains(github.ref_name, 'rc') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') }} + body_path: ./RELEASE_CHANGELOG + files: | + panel.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f872744cf3..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Create Release -on: - push: - tags: - - 'v*' -jobs: - release: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: '12' - - - name: Create release branch and bump version - env: - REF: ${{ github.ref }} - run: | - BRANCH=release/${REF:10} - git config --local user.email "ci@pterodactyl.io" - git config --local user.name "Pterodactyl CI" - git checkout -b $BRANCH - git push -u origin $BRANCH - sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php - git add config/app.php - git commit -m "bump version for release" - git push - - - name: Build assets - run: | - yarn install - yarn run build:production - - - name: Create release archive - run: | - rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.dusk.xml phpunit.xml Vagrantfile - tar -czf panel.tar.gz * .env.example .eslintignore .eslintrc.yml .babel-plugin-macrosrc.js - - - name: Extract changelog - id: extract_changelog - env: - REF: ${{ github.ref }} - run: | - sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG - echo ::set-output name=version_name::`sed -nr "s/^## (${REF:10} .*)$/\1/p" CHANGELOG.md` - - - name: Create checksum and add to changelog - run: | - SUM=`sha256sum panel.tar.gz` - echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG - echo $SUM > checksum.txt - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ steps.extract_changelog.outputs.version_name }} - body_path: ./RELEASE_CHANGELOG - draft: true - prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }} - - - name: Upload binary - id: upload-release-archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: panel.tar.gz - asset_name: panel.tar.gz - asset_content_type: application/gzip - - - name: Upload checksum - id: upload-release-checksum - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./checksum.txt - asset_name: checksum.txt - asset_content_type: text/plain diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index a59f99a476..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Run Tests -on: - push: - branches: - - 'develop' - - 'v2' - pull_request: -jobs: - tests: - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" - strategy: - fail-fast: false - matrix: - php: [ 7.4, 8.0 ] - database: [ 'mariadb:10.2', 'mysql:8' ] - services: - database: - image: ${{ matrix.database }} - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: panel_test - ports: - - 3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - name: "php-${{ matrix.php }} (${{ matrix.database }})" - steps: - - uses: actions/checkout@v2 - - name: get cache directory - id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v2 - with: - path: | - ~/.php_cs.cache - ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip - tools: composer:v2 - coverage: none - - run: cp .env.ci .env - - run: composer install --prefer-dist --no-interaction --no-progress - - run: vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff --config .php-cs-fixer.dist.php - continue-on-error: true - - name: execute unit tests - run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit - if: ${{ always() }} - env: - DB_CONNECTION: testing - TESTING_DB_HOST: UNIT_NO_DB - - name: execute integration tests - run: vendor/bin/phpunit tests/Integration - env: - TESTING_DB_PORT: ${{ job.services.database.ports[3306] }} - TESTING_DB_USERNAME: root diff --git a/.gitignore b/.gitignore index 658743e223..f59b05d1f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /vendor *.DS_Store* !.env.ci -!.env.dusk !.env.example .env* .vagrant/* @@ -9,32 +8,30 @@ storage/framework/* /.idea /nbproject +/.direnv node_modules *.log _ide_helper.php _ide_helper_models.php .phpstorm.meta.php -.php_cs.cache .yarn public/assets/manifest.json # For local development with docker # Remove if we ever put the Dockerfile in the repo .dockerignore -#Dockerfile -docker-compose.yml +docker-compose.* +!docker-compose.example.yml +!docker-compose.example.yaml # for image related files misc -.phpstorm.meta.php -.php_cs.cache - +.php-cs-fixer.cache coverage.xml - -# Vagrant -*.log resources/lang/locales.js -resources/assets/pterodactyl/scripts/helpers/ziggy.js -resources/assets/scripts/helpers/ziggy.js .phpunit.result.cache + +/public/build +/public/hot +result diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 7c7676810f..75a609e964 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,20 +2,25 @@ use PhpCsFixer\Config; use PhpCsFixer\Finder; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; $finder = (new Finder()) - ->in(__DIR__) - ->exclude([ - 'vendor', - 'node_modules', - 'storage', - 'bootstrap/cache', - ]) - ->notName(['_ide_helper*']); + ->name('*.php') + ->ignoreVCSIgnored(true) + ->exclude([__DIR__ . '/bootstrap/cache']) + ->in([ + __DIR__ . '/app', + __DIR__ . '/bootstrap', + __DIR__ . '/config', + __DIR__ . '/database', + __DIR__ . '/routes', + __DIR__ . '/tests', + ]); return (new Config()) - ->setRiskyAllowed(true) ->setFinder($finder) + ->setUsingCache(true) + ->setParallelConfig(ParallelConfigFactory::detect()) ->setRules([ '@Symfony' => true, '@PSR1' => true, @@ -25,11 +30,11 @@ 'combine_consecutive_unsets' => true, 'concat_space' => ['spacing' => 'one'], 'heredoc_to_nowdoc' => true, - 'no_alias_functions' => true, - 'no_unreachable_default_argument_value' => true, + // 'no_alias_functions' => true, + // 'no_unreachable_default_argument_value' => true, 'no_useless_return' => true, 'ordered_imports' => [ - 'sortAlgorithm' => 'length', + 'sort_algorithm' => 'length', ], 'phpdoc_align' => [ 'align' => 'left', @@ -42,7 +47,8 @@ 'var', ], ], - 'random_api_migration' => true, + 'phpdoc_no_alias_tag' => false, + // 'random_api_migration' => true, 'ternary_to_null_coalescing' => true, 'yoda_style' => [ 'equal' => false, diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..d000a2241e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": true, + "endOfLine": "lf" +} diff --git a/.yarnclean b/.yarnclean deleted file mode 100644 index 7bbe5ebab9..0000000000 --- a/.yarnclean +++ /dev/null @@ -1 +0,0 @@ -@types/react-native diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a4034494..55aa8ab7eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,379 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.12.3 +### Fixed +* Adds a rate limit when changing email addresses on an account to prevent account enumeration. +* Fixes backups, databases, and allocation creation to properly acquire a lock before checking if users can create more resources on a server. + +### Changed +* Added Java 25 as an option to the default Minecraft eggs. +* Updates Paper install script and adds support for Java 25 to default egg. +* JWTs now require at least one `JwtScope` enum value to be set when generating. Failure to provide a scope will result in an exception being raised. + +## v1.12.2 +### Fixed +* Fixes task execution jobs to correctly dispatch the next job in the chain. +* Fixes dropdown menu not appearing correctly inside modal when transferring a server. +* Fixes startup variables logging as changed in the activity log even when no change was actually made. +* Fixes multiple issues with the docker image. +* Fixes server transfers getting stuck due to incorrect permission checks in the API. + +## v1.12.1 +### Fixed +* [CVE-2026-26016](https://github.com/pterodactyl/panel/security/advisories/GHSA-g7vw-f8p5-c728) +* [GHSA-hr7j-63v7-vj7g](https://github.com/pterodactyl/panel/security/advisories/GHSA-hr7j-63v7-vj7g) +* Fixes bug where presigned URLs would +* Fixes issue where certain input values would cause the activity log screen to stop rendering properly due to improper element encoding. +* Fixes improper display of unicode characters in console output. +* Fixes page number not resetting when toggling between "Show My Servers" and "Show All Servers" on the dashboard. + +### Changed +* SFTP sessions are now revoked on nodes when a user changes their password or their account is deleted. +* Remote node access tokens are now scoped to only allow access to servers that belong to the same node. Previously a node could access information and control the installation status for any server in the system. +* The default rate limit for the client API was bumped from `128` to `256` requests per minute. + +### Added +* HTTP responses now include default security headers if not otherwise set. +* Adds modal popup when running a Hytale server that requires additional auth. +* Adds support for administrators to view any application API key that has been created, regardless of the owning account. + +## v1.12.0 +### Fixed + +* [CVE-2025-68954](https://github.com/pterodactyl/panel/security/advisories/GHSA-8c39-xppg-479c) +* [CVE-2025-69197](https://github.com/pterodactyl/panel/security/advisories/GHSA-rgmp-4873-r683) +* [CVE-2025-69198](https://github.com/pterodactyl/panel/security/advisories/GHSA-jw2v-cq5x-q68g) +* Fixes a self-XSS issue when entering random data into boxes while creating a new database host. +* Fixes missing `HttpForbiddenException` import in the backup status controller. +* Fixes issue where scheduled tasks would execute every minute regardless of their configured cron syntax. +* Pressing `Ctrl+Z` to undo while editing a file no longer deletes the initial file content. +* Fixed incorrect error message being returned when attempting to delete your own account as an admin. +* Fixes node description not being settable via the API. +* Fixes 0-bytes files returning an error when attempting to upload. +* Fixes nodes displaying the first available location even when that field was not edited and the node has a different value set. +* Fixes allocation notes not being reset when a server is deleted. ([#5157](https://github.com/pterodactyl/panel/pull/5157)) + +### Changed +* Minimum NodeJS version updated to 22 for building. +* Updated all JS and PHP dependencies to their latest versions (where feasible). +* The endpoint for disabling 2FA on an account using the client API changed from `DELETE /api/client/account/two-factor` to `POST /api/client/account/two-factor/disable` +* `^C` in an egg's stop configuration no longer rewrites itself into the default stop configuration. +* `IBM Plex Sans` font is now bundled with the local assets instead of loading from Google CDNs. +* Upload size on nodes is no longer restricted to a max of 1024MB, any positive integer value can be used. +* Administrators are now listed first when viewing a list of all users on the system. +* Websocket no longer endlessly polls when connection issues are encountered, or when Wings disconnects the user for a reason that should not be re-attempted. + +## v1.11.10 +### Fixed +* Update Laravel to address [CVE-2024-52301](https://github.com/advisories/GHSA-gv7v-rgg6-548h) + +### Changed +* Minimum PHP version is now 8.2 due to Laravel upgrade! + +## v1.11.9 +### Fixed +* Fixed issue with CI not pushing Docker image + +## v1.11.8 +### Fixed +* Fixed an issue where a `DELETE` request was used instead of a `POST`, potentially logging user passwords in plain text if they disable 2FA. + +## v1.11.7 +### Added +* Java 21 to Minecraft eggs + +### Changed +* Updated Minecraft EULA link + +### Fixed +* Fixed backups not ever being marked as completed (#5088) +* Fixed `.7z` files not being detected as a compressed file (#5016) + +## v1.11.6 +### Changed +* Better node ownership checks for internal backup endpoints +* Improved validation rules on `docker_image` fields to prevent invalid inputs + +### Fixed +* Multiple XSS vulnerabilities in the admin area ([GHSA-384w-wffr-x63q](https://github.com/pterodactyl/panel/security/advisories/GHSA-384w-wffr-x63q)) + +## v1.11.5 +### Fixed +* Rust egg using the wrong Docker image, breaking Rust modding frameworks. + +## v1.11.4 +### Added +* Added support for the `server.queryport` option on the Rust egg. +* Added support for the Carbon modding framework to the Rust egg. + +### Changed +* Upgraded to Laravel 10. +* Sensitive data is no longer shown in the CopyOnClick toast notification. + +### Fixed +* Allow SVGs to be edited in the server's file manager. +* Properly validate the request body when creating a backup. +* Fixed issue with schedules running at the wrong time when the panel utilized a timezone with non-hour offsets (such as `Australia/Darwin`). +* Fixes the log directory when running the Panel in a container. +* Fixes the permission name used to check if a user has permission to read files/folders. +* Fixes the ability to unset a server's description through the client API. +* Fixed the MassActionBar on the server's file manager blocking elements below it, preventing them from being interacted with. + +## v1.11.3 +### Changed +* When updating a server's description through the client API, if no value is specified, the description will now remain unchanged. +* When installing the Panel for the first time, the queue driver will now all default to `redis` instead of `sync`. + +### Fixed +* `php artisan p:environment:mail` not correctly setting the right variable for `MAIL_FROM_ADDRESS`. +* Fixed the conflict state rendering on the UI for a server showing `reinstall_failed` as `restoring_backup`. +* Fixed the unknown column `uuid` error when jobs fail, causing them not to get stored correctly. +* Fixed the server task endpoints in the client API not allowing `sequence_id` and `continue_on_failure` to be set. + +## v1.11.2 +### Changed +* Telemetry no longer sends a map of Egg and Nest UUIDs to the number of servers using them. +* Increased the timeout for the decompress files endpoint in the client API from 15 seconds to 15 minutes. + +### Fixed +* Fixed Panel Docker image having a `v` prefix in the version displayed in the admin area. +* Fixed emails using the wrong queue name, causing them to not be sent. +* Fixed the settings keys used for configuring SMTP settings, causing settings to not save properly. +* Fixed the `MAIL_EHLO_DOMAIN` environment variable not being properly backwards compatible with the old `SERVER_NAME` variable. + +## v1.11.1 +### Fixed +* Fixed Panel Docker image showing `canary` as it's version. + +## v1.11.0 +### Changed (since 1.10.4) +* Changed minimum PHP version requirement from `7.4` to `8.0`. +* Upgraded from Laravel 8 to Laravel 9. +* This release requires Wings v1.11.x in order for Server Transfers to work. +* `MB` byte suffixes are now displayed as `MiB` to more accurately reflect the actual value. +* Server re-installation failures are tracked independently of the initial installation process. + +### Fixed (since 1.10.4) +* Node maintenance mode now properly blocks access to servers. +* Fixed the length validation on the Minecraft Forge egg. +* Fixed the password in the JDBC string not being properly URL encoded. +* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs. +* Properly handle a missing `Content-Length` header in the response from the daemon. +* Ensure activity log properties are always returned as an object instead of an empty array. + +### Added (since 1.10.4) +* Added the `server:settings.description` activity log event for when a server description is changed. +* Added the ability to cancel file uploads in the file manager for a server. +* Added a telemetry service to collect anonymous metrics from the panel, this feature is *enabled* by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable. + +## v1.11.0-rc.2 +### Changed +* `MB` byte suffixes are now displayed as `MiB` to more accurately reflect the actual value. +* Server re-installation failures are tracked independently of the initial installation process. + +### Fixed +* Properly handle a missing `Content-Length` header in the response from the daemon. +* Ensure activity log properties are always returned as an object instead of an empty array. + +### Added +* Added the `server:settings.description` activity log event for when a server description is changed. +* Added the ability to cancel file uploads in the file manager for a server. +* Added a telemetry service to collect anonymous metrics from the panel, this feature is disabled by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable. + +## v1.11.0-rc.1 +### Changed +* Changed minimum PHP version requirement from `7.4` to `8.0`. +* Upgraded from Laravel 8 to Laravel 9. +* This release requires Wings v1.11.x in order for Server Transfers to work. + +### Fixed +* Node maintenance mode now properly blocks access to servers. +* Fixed the length validation on the Minecraft Forge egg. +* Fixed the password in the JDBC string not being properly URL encoded. +* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs. + +## v1.10.4 +### Fixed +* Fixed an issue where subusers could be given permissions that are not actually registered or used. +* Fixed an issue where node FQDNs could not just be IP addresses. + +### Changed +* Change maximum number of API keys per user from `10` to `25`. +* Change byte unit prefix from `B` to `iB` to better reflect our usage of base 2 (multiples of 1024). + +## v1.10.3 +### Fixed +* S3 Backup driver now supports Cloudflare R2. +* Node FQDNs can now be used with AAAA records with no A records present. +* Server transfers can no longer be initiated if the server is being installed, transferred, or restoring a backup. +* Fixed an issue relating to the use of arrays in the `config_files` field with eggs. +* Fixed `oom_disabled` not being mapped in the Application API when creating a new server. + +### Added +* File manager now supports selecting multiple files for upload (when using the upload button). +* Added a configuration option for specifying the S3 storage class for backups. + +### Changed +* Servers will now show the current uptime when the server is starting rather than only showing when the server is marked as online. + +## v1.10.2 +### Fixed +* Fixes a rendering issue with egg descriptions in the admin area +* Fixes the page title on the SSH Keys page + +### Changed +* Additional validation rules will now show a toggle switch rather than an input when editing server variables +* The eggs endpoint will now always return an empty JSON object for the `config_files` field, even if the field is completely empty + +### Added +* Adds a `Force Outgoing IP` option for eggs that can be used to ensure servers making outgoing connections use their allocation IP rather than the node's primary ip +* Adds options to configure sending of email (re)install notifications +* Add an option to configure the part size for backups uploaded to S3 + +## v1.10.1 +### Fixed +* Fixes a surprise `clock()` function that was used for debugging and should not have made it into the release. This was causing activity events to not properly sync between the Panel and Wings. + +## v1.10.0 +### Fixed +* Fixes improper cache key naming on the frontend causing server activity logs to be duplicated across server page views. +* Fixes overflow issues on dialogs when the internal content is too long. +* Fixes spinner overlay on console improperly taking up the entire page making it impossible to use navigation controls. +* Fixes 2FA QR code background being too dark for some phones to properly scan. +* File manager now properly displays an error message if a user attempts to upload a folder rather than files. +* Fixes the "Create Directory" dialog persisting the previously entered value when it is re-opened. + +### Changed +* IP addresses in activity logs are now always displayed to administrators, regardless of if they own the server or not. +* Scroll down indicator on the console has been changed to a down arrow to be clearer. +* Docker builds have been updated to use `PHP 8.1`. +* Recaptcha validation domain is now configurable using the `RECAPTCHA_DOMAIN` environment variable. +* Drag and drop overlay on the file manager has been tweaked to be slightly more consistent with the frontend style and be a little easier to read. + +### Added +* Adds support for the `user_uuid` claim on all generated JWTs which allows Wings to properly identify the user performing each action. +* Adds support for recieving external activity log events from Wings instances (power state, commands, SFTP, and uploads). +* Adds support for tracking failed password-based SFTP logins. +* Server name and description are now passed along to Wings making them available in egg variables for parsing and including. +* Adds support for displaying all active file uploads in the file manager. + +## v1.9.2 +### Fixed +* Fixes rouding in sidebar of CPU usage graph that was causing an excessive number of zeros to be rendered. +* Fixes the Java Version selector modal having the wrong default value selected initially. +* Fixes console rendering in Safari that was causing the console to resize excessively and graphs to overlay content. +* Fixes missing "Starting"/"Stopping" status display in the server uptime block. +* Fixes incorrect formatting of activity log when viewing certain file actions. + +### Changed +* Updated the UI for the two-step authorization setup on accounts to use new Dialog UI and provide better clarity to new users. + +### Added +* Added missing `` tag to template output to avoid entering quirks mode in browsers. +* Added password requirement when enabling TOTP on an account. + +## v1.9.1 +### Fixed +* Fixes missing "Click to Copy" for server address on the console data blocks. +* Fixes data points on the graphs not being properly rounded to two decimal places. +* Returns byte formatting logic to use `1024` as the base value, rather than `1000`. +* Fixes permission error occurring when a server is marked as installing and an admin navigates to the console screen. +* Fixes improper display of install/transfer warning on the server console page. +* Fixes permission matching for the server settings page to correctly allow access when a user has _any_ of the needed permissions. + +### Changed +* Moves the server data blocks to the right-hand side of the console, rather than the left. +* Rather than defaulting graph values at `0` when resetting or refreshing the page, their values are now hidden entirely. +* **[security]** Hides IP addresses from all activity log entries that are not directly associated with the currently signed in user. + +### Added +* Adds the current resource limits for a server next to each data block on the console screen. + +## v1.9.0 +### Added +* Added support for using Tailwind classes inside components using `className={}` rather than having to use `twin.macro` with the `css={}` prop. +* Added HeadlessUI and Heroicons packages. +* Added new `Tooltip.tsx` component to support displaying tooltips within the Panel. +* Adds a new activity log view for both user accounts and individual servers. This builds upon data collected in previous releases. +* Added a new column `api_key_id` to the `activity_logs` table to indicate if the user performed the action while using an API key. +* Adds initial support for language translations on the front-end. The underlying implementation details are working, however work has not yet begun on actually translating all of the strings yet. Expect this to continue in future releases. +* Improved accessibility for navigation icons by adding a tooltip on hover to indicate what each one does. +* Adds logging for API keys that are blocked from performing an API action due to IP address limiting. +* Adds support for `?filter[description]=foo` when querying servers on both the client and application API. + +### Changed +* Updated how release assets are generated to perform more logical bundle splitting. This should help reduce the amount of data users have to download at once in order to render the UI. +* Upgraded From TailwindCSS 2 to 3 — for most people this should have minimal if any impact. +* Chart.js updated from v2 to v3. +* Reduced the number of custom colors in use — by default we now use Tailwind's default color pallet, with the exception of a custom gray scheme. +* **[deprecated]** The use of `neutral` and `primary` have been deprecated in class names, prefer `gray` and `blue` respectively. +* Begins the process of dropping the use of Gravatars for user avatars and replaces them with dynamically generated SVG images. +* Improved front-end route definitions to make it easier for external modifications to inject their routes and components into the codebase without having to modify as many core files. +* Redesigned the server console screen to better display data users might be looking for, and increase the height of the console itself. +* Merged the two network data graphs into a single dual-line graph to better display incoming and outgoing data volumes. +* Updated all byte formatting logic to use `1000` as the divisor rather than `1024` to be more consistent with what users most likely expect. +* Changed the underlying `eslint` rules applied to the front-end codebase to simplify them dramatically. We now utilize `prettier` in combination with some basic default rulesets to make it easier to understand the expected formatting. + +### Fixed +* Fixes a bug causing a 404 error when attempting to delete a database from a server in the admin control panel. +* Fixes console input auto-capitalizing and auto-correcting when entering text on some mobile devices. +* Fixes SES service configuration using a hard-coded `us-east-1` region. +* Fixes a bug causing a 404 error when attempting to delete an SSH key from your account when the SHA256 hash includes a slash. +* Fixes mobile keyboards automatically attempting to capitalize and spellcheck typing on the server console. +* Fixes improper support for IP address CIDR ranges when creating API keys for the client area. +* Fixes a bug preventing additional included details from being returned from the application API when utilizing a client API key as an administrator. + +## v1.8.1 +### Fixed +* Fixes a bug causing mounts to return a 404 error when adding them to a server. +* Fixes a bug causing the Egg Image dropdown to not display properly when creating a new server. +* Fixes a bug causing an error when attemping to create a new server via the API. + +## v1.8.0 +**Important:** this version updates the `version` field on generated Eggs to be `PTDL_v2` due to formatting changes. This +should be completely seamless for most installations as the Panel is able to convert between the two. Custom solutions +using these eggs should be updated to account for the new format. + +This release also changes API key behavior — "client" keys belonging to admin users can now be used to access +the `/api/application` endpoints in their entirety. Existing "application" keys generated in the admin area should +be considered deprecated, but will continue to work. Application keys _will not_ work with the client API. + +### Fixed +* Schedules are no longer run when a server is suspended or marked as installing. +* The remote field when creating a database is no longer limited to an IP address and `%` wildcard — all expected MySQL remote host values are allowed. +* Allocations cannot be deleted from a server by a user if the server is configured with an `allocation_limit` set to `0`. +* The Java Version modal no longer shows a dropdown and update option to users that do not have permission to make those changes. +* The Java Version modal now correctly returns only the images available to the server's selected Egg. +* Fixes leading and trailing spaces being removed from variable values on file manager endpoints, causing errors when trying to perform actions against certain files and folders. + +### Changed +* Forces HTTPS on URLs when the `APP_URL` value is set and includes `https://` within the URL. This addresses proxy misconfiguration issues that would cause URLs to be generated incorrectly. +* Lowers the default timeout values for requests to Wings instances from 10 seconds to 5 seconds. +* Additional permissions (`CREATE TEMPORARY TABLES`, `CREATE VIEW`, `SHOW VIEW`, `EVENT`, and `TRIGGER`) are granted to users when creating new databases for servers. +* development: removed Laravel Debugbar in favor of Clockwork for debugging. +* The 2FA input field when logging in is now correctly identified as `one-time-password` to help browser autofill capabilities. +* Changed API authentication mechanisms to make use of Laravel Sanctum to significantly clean up our internal handling of sessions. +* API keys generated by the system now set a prefix to identify them as Pterodactyl API keys, and if they are client or application keys. This prefix looks like `ptlc_` for client keys, and `ptla_` for application keys. Existing API keys are unaffected by this change. + +### Added +* Added support for PHP 8.1 in addition to PHP 8.0 and 7.4. +* Adds more support for catching potential PID exhaustion errors in different games. +* It is now possible to create a new node on the Panel using an artisan command. +* A new cron cheatsheet has been added which appears when creating a schedule. +* Adds support for filtering the `/api/application/nodes/:id/allocations` endpoint using `?filter[server_id]=0` to only return allocations that are not currently assigned to a server on that node. +* Adds support for naming docker image values in an Egg to improve front-end display capabilities. +* Adds command to return the configuration for a specific node in both YAML and JSON format (`php artisan p:node:configuration`). +* Adds command to return a list of all nodes available on the Panel in both table and JSON format (`php artisan p:node:list`). +* Adds server network (inbound/outbound) usage graphs to the console screen. +* Adds support for configuring CORS on the API by setting the `APP_CORS_ALLOWED_ORIGINS=example.com,dashboard.example.com` environment variable. By default all instances are configured with this set to `*` which allows any origin. +* Adds proper activity logging for the following areas of the Panel: authentication, user account modifications, server modification. This is an initial test implementation before further roll-out in the software. Events are logged into the database but are not currently exposed in the UI — they will be displayed in a future update. + +### Removed +* Removes Google Analytics from the front end code. +* Removes multiple middleware that were previously used for configuring API access and controlling model fetching. This has all been replaced with Laravel Sanctum and standard Laravel API tooling. This should make codebase discovery significantly more simple. +* **DEPRECATED**: The use of `Pterodactyl\Models\AuditLog` is deprecated and all references to this model have been removed from the codebase. In the next major release this model and table will be fully dropped. + ## v1.7.0 ### Fixed * Fixes typo in message shown to user when deleting a database. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index d002649997..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at support@pterodactyl.io. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80a863c54d..99f8ff0185 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,33 @@ # Contributing -Pterodactyl does not accept Pull Requests (PRs) _for new functionality_ from users that are not currently part of the -core project team. It has become overwhelming to try and give the proper time and attention that such complicated PRs -tend to require — and deserve. As a result, it is in the project's best interest to limit the scope of work on -new functionality to work done within the core project team. -PRs that address existing _bugs_ with a corresponding issue opened in our issue tracker will continue to be accepted -and reviewed. Their scope is often signficantly more targeted, and simply improving upon existing and well defined -logic. +**Pterodactyl does not accept Pull Requests (PRs) _for new functionality_** that was not previously approved by project +maintainers on GitHub discussions. It has become overwhelming to try and give the proper time and attention that such +complicated PRs tend to require. As a result, it is in the project's best interest to limit the scope of work on new +functionality to work done within the core project team. -### Responsible Disclosure -This is a fairly in-depth project and makes use of a lot of parts. We strive to keep everything as secure as possible -and welcome you to take a look at the code provided in this project yourself. We do ask that you be considerate of -others who are using the software and not publicly disclose security issues without contacting us first by email. +PRs that address existing bugs or features **with a corresponding issue opened in our issue tracker** will continue to +be accepted and reviewed. Their scope is often significantly more targeted, and simply improving upon existing and well +defined logic. + +## AI Assistance + +If you use any type of AI assistance while developing for Pterodactyl **it must be disclosed** within the +corresponding review PR and you must identify what it was used for. All interaction with the community must be written +by a human. **You MUST NOT use AI to compose any PR titles, descriptions, or comments**, and must not use AI to +interact with the GitHub Discussions community. -We'll make a deal with you: if you contact us by email and we fail to respond to you within a week you are welcome to -publicly disclose whatever issue you have found. We understand how frustrating it is when you find something big and -no one will respond to you. This holds us to a standard of providing prompt attention to any issues that arise and -keeping this community safe. +## Responsible Disclosure + +This is an in-depth project making use of many moving pieces. While we strive to keep everything as secure as possible +and welcome you to take a look at the code provided in this project yourself. We do ask that you be considerate of +others who are using the software and not publicly disclose security issues. Please see [`SECURITY.md`](/SECURITY.md) +for information on how to report security issues to the team. -If you've found what you believe is a security issue please email `dane åt pterodactyl døt io`. +## Contact Us -### Contact Us -You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a -bug or other problems, open an issue on here for us to take a look at it. We also accept feature requests here as well. +You can find us in a couple places online. First and foremost, we're active right here on GitHub. If you encounter a +bug or other problems, open an issue on here for us to take a look at it. Please make use of +our [GitHub Discussions](https://github.com/orgs/pterodactyl/discussions/categories/feature-requests) +for any feature requests, general questions, or help with the software. You can also find us on [Discord](https://discord.gg/pterodactyl). diff --git a/Dockerfile b/Dockerfile index 2c743cab21..d7ddd49b5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # Build the assets that are needed for the frontend. This build stage is then discarded # since we won't need NodeJS anymore in the future. This Docker image ships a final production # level distribution of Pterodactyl. -FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14 +FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine WORKDIR /app COPY . ./ RUN yarn install --frozen-lockfile \ @@ -10,11 +10,11 @@ RUN yarn install --frozen-lockfile \ # Stage 1: # Build the actual container with all of the needed PHP dependencies that will run the application. -FROM --platform=$TARGETOS/$TARGETARCH php:7.4-fpm-alpine +FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine WORKDIR /app COPY . ./ COPY --from=0 /app/public/assets ./public/assets -RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot certbot-nginx \ +RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot certbot-nginx mysql-client \ && docker-php-ext-configure zip \ && docker-php-ext-install bcmath gd pdo_mysql zip \ && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ @@ -23,6 +23,7 @@ RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar un && chmod 777 -R bootstrap storage \ && composer install --no-dev --optimize-autoloader \ && rm -rf .env bootstrap/cache/*.php \ + && mkdir -p /app/storage/logs/ \ && chown -R nginx:nginx . RUN rm /usr/local/etc/php-fpm.conf \ diff --git a/README.md b/README.md index 998e39cd7a..cee480ac24 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ [![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pterodactyl/panel/tests?label=Tests&style=for-the-badge) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pterodactyl/panel/ci.yaml?label=Tests&style=for-the-badge&branch=1.0-develop) ![Discord](https://img.shields.io/discord/122900397965705216?label=Discord&logo=Discord&logoColor=white&style=for-the-badge) ![GitHub Releases](https://img.shields.io/github/downloads/pterodactyl/panel/latest/total?style=for-the-badge) ![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge) # Pterodactyl Panel -Pterodactyl® is a free, open-source game server management panel built with PHP, React, and Go. Designed with security + +Pterodactyl® is a free, open-source game server management panel built with PHP, React, and Go. Designed with security in mind, Pterodactyl runs all game servers in isolated Docker containers while exposing a beautiful and intuitive UI to end users. @@ -15,38 +16,28 @@ Stop settling for less. Make game servers a first class citizen on your platform ![Image](https://cdn.pterodactyl.io/site-assets/pterodactyl_v1_demo.gif) ## Documentation + * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) * [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html) * [Community Guides](https://pterodactyl.io/community/about.html) * Or, get additional help [via Discord](https://discord.gg/pterodactyl) ## Sponsors -I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement. -[Interested in becoming a sponsor?](https://github.com/sponsors/DaneEveritt) - -| Company | About | -| ------- | ----- | -| [**WISP**](https://wisp.gg) | Extra features. | -| [**MixmlHosting**](https://mixmlhosting.com) | MixmlHosting provides high quality Virtual Private Servers along with game servers, all at a affordable price. | -| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | -| [**Tempest**](https://tempest.net/) | Tempest Hosting is a subsidiary of Path Network, Inc. offering **unmetered DDoS protected 10Gbps dedicated servers**, starting at just $80/month. Full anycast, tons of filters. | -| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. | -| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | -| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. | -| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | -| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. | -| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. | -| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | -| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | -| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | -| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | -| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | -| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.| -| [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! | -| [**HostEZ**](https://hostez.io) | Providing North America Valheim, Minecraft and other popular games with low latency, high uptime and maximum availability. EZ! | + +I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's development. +[Interested in becoming a sponsor?](https://github.com/sponsors/pterodactyl) + +| Company | About | +|-----------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | +| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | +| [**HostEZ**](https://hostez.io) | US & EU Rust & Minecraft Hosting. DDoS Protected bare metal, VPS and colocation with low latency, high uptime and maximum availability. EZ! | +| [**Blueprint**](https://blueprint.zip/?utm_source=pterodactyl&utm_medium=sponsor) | Create and install Pterodactyl addons and themes with the growing Blueprint framework - the package-manager for Pterodactyl. Use multiple modifications at once without worrying about conflicts and make use of the large extension ecosystem. | +| [**indifferent broccoli**](https://indifferentbroccoli.com/) | indifferent broccoli is a game server hosting and rental company. With us, you get top-notch computer power for your gaming sessions. We destroy lag, latency, and complexity--letting you focus on the fun stuff. | ### Supported Games + Pterodactyl supports a wide variety of games by utilizing Docker containers to isolate each instance. This gives you the power to run game servers without bloating machines with a host of additional dependencies. @@ -72,9 +63,10 @@ and there are plenty more games available provided by the community. Some of the * Xonotic * Starmade * Discord ATLBot, and most other Node.js/Python discord bots -* [and many more...](https://github.com/parkervcp/eggs) +* [and many more...](https://pterodactyleggs.com) ## License + Pterodactyl® Copyright © 2015 - 2022 Dane Everitt and contributors. Code released under the [MIT License](./LICENSE.md). diff --git a/SECURITY.md b/SECURITY.md index 673a38ee32..1c09922612 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,18 +1,24 @@ # Security Policy ## Supported Versions -The following versions of Pterodactyl are receiving active support and maintenance. Any security vulnerabilities discovered must be reproducible in supported versions. -| Panel | Daemon | Supported | -| ----- | ------------ | ------------------ | -| 1.7.x | wings@1.5.x | :white_check_mark: | -| 0.7.x | daemon@0.6.x | :x: | +Pterodactyl only provides security support for the latest `major.minor` versions of the Panel and Wings software. +If a security vulnerability is found in an older version but cannot be reproduced on a supported version it will +not be considered. Additionally, security issues found in unreleased code will be addressed, but do not warrant a +security advisory. +For example, if the latest version of the Panel is `1.2.5` then we only support security reports for issues that +occur on `>= 1.2.x` versions of the Panel software. The Panel and Wings have their own versions, but they generally +follow eachother. ## Reporting a Vulnerability -Please reach out directly to any project team member on Discord when reporting a security vulnerability, or you can send an email to `dane@pterodactyl.io`. +Please use our GitHub Security reporting meachnism to quickly alert the team to any security issues you come across, +or send an email to `security@pterodactyl.io` with the details of your report. -We make every effort to respond as soon as possible, although it may take a day or two for us to sync internally and determine the severity of the report and its impact. Please, _do not_ use a public facing channel or GitHub issues to report sensitive security issues. +We make every effort to respond as soon as possible, although it may take a day or two for us to sync internally and +determine the severity of the report and its impact. Please, _do not_ use a public facing channel or GitHub issues to +report sensitive security issues. -As part of our process, we will create a security advisory for the affected versions and disclose it publicly, usually two to four weeks after a releasing a version that addresses it. +As part of our process, we will create a security advisory for the affected versions and disclose it publicly, usually +two to four weeks after a releasing a version that addresses it. diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 2746d1a4f4..b143719fe7 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -1,32 +1,22 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Environment; -use DateTimeZone; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use Illuminate\Validation\Factory as ValidatorFactory; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; -use Illuminate\Contracts\Config\Repository as ConfigRepository; class AppSettingsCommand extends Command { use EnvironmentWriterTrait; - public const ALLOWED_CACHE_DRIVERS = [ + public const CACHE_DRIVERS = [ 'redis' => 'Redis (recommended)', 'memcached' => 'Memcached', 'file' => 'Filesystem', ]; - public const ALLOWED_SESSION_DRIVERS = [ + public const SESSION_DRIVERS = [ 'redis' => 'Redis (recommended)', 'memcached' => 'Memcached', 'database' => 'MySQL Database', @@ -34,30 +24,14 @@ class AppSettingsCommand extends Command 'cookie' => 'Cookie', ]; - public const ALLOWED_QUEUE_DRIVERS = [ + public const QUEUE_DRIVERS = [ 'redis' => 'Redis (recommended)', 'database' => 'MySQL Database', 'sync' => 'Sync', ]; - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - protected $command; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var string - */ protected $description = 'Configure basic environment settings for the Panel.'; - /** - * @var string - */ protected $signature = 'p:environment:setup {--new-salt : Whether or not to generate a new salt for Hashids.} {--author= : The email that services created on this instance should be linked to.} @@ -69,23 +43,17 @@ class AppSettingsCommand extends Command {--redis-host= : Redis host to use for connections.} {--redis-pass= : Password used to connect to redis.} {--redis-port= : Port to connect to redis over.} - {--settings-ui= : Enable or disable the settings UI.}'; + {--settings-ui= : Enable or disable the settings UI.} + {--telemetry= : Enable or disable anonymous telemetry.}'; - /** - * @var array - */ - protected $variables = []; + protected array $variables = []; /** * AppSettingsCommand constructor. */ - public function __construct(ConfigRepository $config, Kernel $command, ValidatorFactory $validator) + public function __construct(private Kernel $console) { parent::__construct(); - - $this->config = $config; - $this->command = $command; - $this->validator = $validator; } /** @@ -93,79 +61,81 @@ public function __construct(ConfigRepository $config, Kernel $command, Validator * * @throws \Pterodactyl\Exceptions\PterodactylException */ - public function handle() + public function handle(): int { - if (empty($this->config->get('hashids.salt')) || $this->option('new-salt')) { + if (empty(config('hashids.salt')) || $this->option('new-salt')) { $this->variables['HASHIDS_SALT'] = str_random(20); } - $this->output->comment(trans('command/messages.environment.app.author_help')); + $this->output->comment('Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.'); $this->variables['APP_SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask( - trans('command/messages.environment.app.author'), - $this->config->get('pterodactyl.service.author', 'unknown@unknown.com') + 'Egg Author Email', + config('pterodactyl.service.author', 'unknown@unknown.com') ); - $validator = $this->validator->make( - ['email' => $this->variables['APP_SERVICE_AUTHOR']], - ['email' => 'email'] - ); + if (!filter_var($this->variables['APP_SERVICE_AUTHOR'], FILTER_VALIDATE_EMAIL)) { + $this->output->error('The service author email provided is invalid.'); - if ($validator->fails()) { - foreach ($validator->errors()->all() as $error) { - $this->output->error($error); - } return 1; } - $this->output->comment(trans('command/messages.environment.app.app_url_help')); + $this->output->comment('The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.'); $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( - trans('command/messages.environment.app.app_url'), - $this->config->get('app.url', 'http://example.org') + 'Application URL', + config('app.url', 'https://example.com') ); - $this->output->comment(trans('command/messages.environment.app.timezone_help')); + $this->output->comment('The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.'); $this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate( - trans('command/messages.environment.app.timezone'), - DateTimeZone::listIdentifiers(DateTimeZone::ALL), - $this->config->get('app.timezone') + 'Application Timezone', + \DateTimeZone::listIdentifiers(), + config('app.timezone') ); - $selected = $this->config->get('cache.default', 'redis'); + $selected = config('cache.default', 'redis'); $this->variables['CACHE_DRIVER'] = $this->option('cache') ?? $this->choice( - trans('command/messages.environment.app.cache_driver'), - self::ALLOWED_CACHE_DRIVERS, - array_key_exists($selected, self::ALLOWED_CACHE_DRIVERS) ? $selected : null + 'Cache Driver', + self::CACHE_DRIVERS, + array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null ); - $selected = $this->config->get('session.driver', 'redis'); + $selected = config('session.driver', 'redis'); $this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice( - trans('command/messages.environment.app.session_driver'), - self::ALLOWED_SESSION_DRIVERS, - array_key_exists($selected, self::ALLOWED_SESSION_DRIVERS) ? $selected : null + 'Session Driver', + self::SESSION_DRIVERS, + array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null ); - $selected = $this->config->get('queue.default', 'redis'); + $selected = config('queue.default', 'redis'); $this->variables['QUEUE_CONNECTION'] = $this->option('queue') ?? $this->choice( - trans('command/messages.environment.app.queue_driver'), - self::ALLOWED_QUEUE_DRIVERS, - array_key_exists($selected, self::ALLOWED_QUEUE_DRIVERS) ? $selected : null + 'Queue Driver', + self::QUEUE_DRIVERS, + array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null ); if (!is_null($this->option('settings-ui'))) { $this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true'; } else { - $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(trans('command/messages.environment.app.settings'), true) ? 'false' : 'true'; + $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm('Enable UI based settings editor?', true) ? 'false' : 'true'; } + $this->output->comment('Please reference https://pterodactyl.io/panel/1.0/additional_configuration.html#telemetry for more detailed information regarding telemetry data and collection.'); + $this->variables['PTERODACTYL_TELEMETRY_ENABLED'] = $this->option('telemetry') ?? $this->confirm( + 'Enable sending anonymous telemetry data?', + config('pterodactyl.telemetry.enabled', true) + ) ? 'true' : 'false'; + // Make sure session cookies are set as "secure" when using HTTPS - if (strpos($this->variables['APP_URL'], 'https://') === 0) { + if (str_starts_with($this->variables['APP_URL'], 'https://')) { $this->variables['SESSION_SECURE_COOKIE'] = 'true'; } $this->checkForRedis(); $this->writeToEnvironment($this->variables); - $this->info($this->command->output()); + $this->info($this->console->output()); + + return 0; } /** @@ -182,22 +152,22 @@ private function checkForRedis() return; } - $this->output->note(trans('command/messages.environment.app.using_redis')); + $this->output->note('You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.'); $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( - trans('command/messages.environment.app.redis_host'), - $this->config->get('database.redis.default.host') + 'Redis Host', + config('database.redis.default.host') ); $askForRedisPassword = true; - if (!empty($this->config->get('database.redis.default.password'))) { - $this->variables['REDIS_PASSWORD'] = $this->config->get('database.redis.default.password'); - $askForRedisPassword = $this->confirm(trans('command/messages.environment.app.redis_pass_defined')); + if (!empty(config('database.redis.default.password'))) { + $this->variables['REDIS_PASSWORD'] = config('database.redis.default.password'); + $askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?'); } if ($askForRedisPassword) { - $this->output->comment(trans('command/messages.environment.app.redis_pass_help')); + $this->output->comment('By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.'); $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( - trans('command/messages.environment.app.redis_password') + 'Redis Password' ); } @@ -206,8 +176,8 @@ private function checkForRedis() } $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( - trans('command/messages.environment.app.redis_port'), - $this->config->get('database.redis.default.port') + 'Redis Port', + config('database.redis.default.port') ); } } diff --git a/app/Console/Commands/Environment/DatabaseSettingsCommand.php b/app/Console/Commands/Environment/DatabaseSettingsCommand.php index b983fcc9f6..fb4a2e25b2 100644 --- a/app/Console/Commands/Environment/DatabaseSettingsCommand.php +++ b/app/Console/Commands/Environment/DatabaseSettingsCommand.php @@ -1,48 +1,18 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Environment; -use PDOException; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\DatabaseManager; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; -use Illuminate\Contracts\Config\Repository as ConfigRepository; class DatabaseSettingsCommand extends Command { use EnvironmentWriterTrait; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - protected $console; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - - /** - * @var string - */ protected $description = 'Configure database settings for the Panel.'; - /** - * @var string - */ protected $signature = 'p:environment:database {--host= : The connection address for the MySQL server.} {--port= : The connection port for the MySQL server.} @@ -50,71 +20,62 @@ class DatabaseSettingsCommand extends Command {--username= : Username to use when connecting.} {--password= : Password to use for this database.}'; - /** - * @var array - */ - protected $variables = []; + protected array $variables = []; /** * DatabaseSettingsCommand constructor. */ - public function __construct(ConfigRepository $config, DatabaseManager $database, Kernel $console) + public function __construct(private DatabaseManager $database, private Kernel $console) { parent::__construct(); - - $this->config = $config; - $this->console = $console; - $this->database = $database; } /** * Handle command execution. * - * @return int - * * @throws \Pterodactyl\Exceptions\PterodactylException */ - public function handle() + public function handle(): int { - $this->output->note(trans('command/messages.environment.database.host_warning')); + $this->output->note('It is highly recommended to not use "localhost" as your database host as we have seen frequent socket connection issues. If you want to use a local connection you should be using "127.0.0.1".'); $this->variables['DB_HOST'] = $this->option('host') ?? $this->ask( - trans('command/messages.environment.database.host'), - $this->config->get('database.connections.mysql.host', '127.0.0.1') + 'Database Host', + config('database.connections.mysql.host', '127.0.0.1') ); $this->variables['DB_PORT'] = $this->option('port') ?? $this->ask( - trans('command/messages.environment.database.port'), - $this->config->get('database.connections.mysql.port', 3306) + 'Database Port', + config('database.connections.mysql.port', 3306) ); $this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask( - trans('command/messages.environment.database.database'), - $this->config->get('database.connections.mysql.database', 'panel') + 'Database Name', + config('database.connections.mysql.database', 'panel') ); - $this->output->note(trans('command/messages.environment.database.username_warning')); + $this->output->note('Using the "root" account for MySQL connections is not only highly frowned upon, it is also not allowed by this application. You\'ll need to have created a MySQL user for this software.'); $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( - trans('command/messages.environment.database.username'), - $this->config->get('database.connections.mysql.username', 'pterodactyl') + 'Database Username', + config('database.connections.mysql.username', 'pterodactyl') ); $askForMySQLPassword = true; - if (!empty($this->config->get('database.connections.mysql.password')) && $this->input->isInteractive()) { - $this->variables['DB_PASSWORD'] = $this->config->get('database.connections.mysql.password'); - $askForMySQLPassword = $this->confirm(trans('command/messages.environment.database.password_defined')); + if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) { + $this->variables['DB_PASSWORD'] = config('database.connections.mysql.password'); + $askForMySQLPassword = $this->confirm('It appears you already have a MySQL connection password defined, would you like to change it?'); } if ($askForMySQLPassword) { - $this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret(trans('command/messages.environment.database.password')); + $this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password'); } try { $this->testMySQLConnection(); - } catch (PDOException $exception) { - $this->output->error(trans('command/messages.environment.database.connection_error', ['error' => $exception->getMessage()])); - $this->output->error(trans('command/messages.environment.database.creds_not_saved')); + } catch (\PDOException $exception) { + $this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage())); + $this->output->error('Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.'); - if ($this->confirm(trans('command/messages.environment.database.try_again'))) { + if ($this->confirm('Go back and try again?')) { $this->database->disconnect('_pterodactyl_command_test'); return $this->handle(); @@ -135,7 +96,7 @@ public function handle() */ private function testMySQLConnection() { - $this->config->set('database.connections._pterodactyl_command_test', [ + config()->set('database.connections._pterodactyl_command_test', [ 'driver' => 'mysql', 'host' => $this->variables['DB_HOST'], 'port' => $this->variables['DB_PORT'], diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php index 57998a2ea2..3a211394c9 100644 --- a/app/Console/Commands/Environment/EmailSettingsCommand.php +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Environment; @@ -17,19 +10,8 @@ class EmailSettingsCommand extends Command { use EnvironmentWriterTrait; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var string - */ protected $description = 'Set or update the email sending configuration for the Panel.'; - /** - * @var string - */ protected $signature = 'p:environment:mail {--driver= : The mail driver to use.} {--email= : Email address that messages from the Panel will originate from.} @@ -41,19 +23,14 @@ class EmailSettingsCommand extends Command {--username=} {--password=}'; - /** - * @var array - */ - protected $variables = []; + protected array $variables = []; /** * EmailSettingsCommand constructor. */ - public function __construct(ConfigRepository $config) + public function __construct(private ConfigRepository $config) { parent::__construct(); - - $this->config = $config; } /** @@ -67,12 +44,12 @@ public function handle() trans('command/messages.environment.mail.ask_driver'), [ 'smtp' => 'SMTP Server', - 'mail' => 'PHP\'s Internal Mail Function', + 'sendmail' => 'sendmail Binary', 'mailgun' => 'Mailgun Transactional Email', 'mandrill' => 'Mandrill Transactional Email', - 'postmark' => 'Postmarkapp Transactional Email', + 'postmark' => 'Postmark Transactional Email', ], - $this->config->get('mail.driver', 'smtp') + $this->config->get('mail.default', 'smtp') ); $method = 'setup' . studly_case($this->variables['MAIL_DRIVER']) . 'DriverVariables'; @@ -80,7 +57,7 @@ public function handle() $this->{$method}(); } - $this->variables['MAIL_FROM'] = $this->option('email') ?? $this->ask( + $this->variables['MAIL_FROM_ADDRESS'] = $this->option('email') ?? $this->ask( trans('command/messages.environment.mail.ask_mail_from'), $this->config->get('mail.from.address') ); @@ -90,12 +67,6 @@ public function handle() $this->config->get('mail.from.name') ); - $this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( - trans('command/messages.environment.mail.ask_encryption'), - ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], - $this->config->get('mail.encryption', 'tls') - ); - $this->writeToEnvironment($this->variables); $this->line('Updating stored environment configuration file.'); @@ -109,22 +80,28 @@ private function setupSmtpDriverVariables() { $this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_host'), - $this->config->get('mail.host') + $this->config->get('mail.mailers.smtp.host') ); $this->variables['MAIL_PORT'] = $this->option('port') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_port'), - $this->config->get('mail.port') + $this->config->get('mail.mailers.smtp.port') ); $this->variables['MAIL_USERNAME'] = $this->option('username') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_username'), - $this->config->get('mail.username') + $this->config->get('mail.mailers.smtp.username') ); $this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret( trans('command/messages.environment.mail.ask_smtp_password') ); + + $this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( + trans('command/messages.environment.mail.ask_encryption'), + ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], + $this->config->get('mail.mailers.smtp.encryption', 'tls') + ); } /** diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php index 4db0be7024..25c7744058 100644 --- a/app/Console/Commands/InfoCommand.php +++ b/app/Console/Commands/InfoCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands; @@ -15,35 +8,16 @@ class InfoCommand extends Command { - /** - * @var string - */ protected $description = 'Displays the application, database, and email configurations along with the panel version.'; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var string - */ protected $signature = 'p:info'; - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - protected $versionService; - /** * VersionCommand constructor. */ - public function __construct(ConfigRepository $config, SoftwareVersionService $versionService) + public function __construct(private ConfigRepository $config, private SoftwareVersionService $versionService) { parent::__construct(); - - $this->config = $config; - $this->versionService = $versionService; } /** @@ -78,33 +52,29 @@ public function handle() $driver = $this->config->get('database.default'); $this->table([], [ ['Driver', $driver], - ['Host', $this->config->get("database.connections.{$driver}.host")], - ['Port', $this->config->get("database.connections.{$driver}.port")], - ['Database', $this->config->get("database.connections.{$driver}.database")], - ['Username', $this->config->get("database.connections.{$driver}.username")], + ['Host', $this->config->get("database.connections.$driver.host")], + ['Port', $this->config->get("database.connections.$driver.port")], + ['Database', $this->config->get("database.connections.$driver.database")], + ['Username', $this->config->get("database.connections.$driver.username")], ], 'compact'); + // TODO: Update this to handle other mail drivers $this->output->title('Email Configuration'); $this->table([], [ - ['Driver', $this->config->get('mail.driver')], - ['Host', $this->config->get('mail.host')], - ['Port', $this->config->get('mail.port')], - ['Username', $this->config->get('mail.username')], + ['Driver', $this->config->get('mail.default')], + ['Host', $this->config->get('mail.mailers.smtp.host')], + ['Port', $this->config->get('mail.mailers.smtp.port')], + ['Username', $this->config->get('mail.mailers.smtp.username')], ['From Address', $this->config->get('mail.from.address')], ['From Name', $this->config->get('mail.from.name')], - ['Encryption', $this->config->get('mail.encryption')], + ['Encryption', $this->config->get('mail.mailers.smtp.encryption')], ], 'compact'); } /** * Format output in a Name: Value manner. - * - * @param string $value - * @param string $opts - * - * @return string */ - private function formatText($value, $opts = '') + private function formatText(string $value, string $opts = ''): string { return sprintf('<%s>%s', $opts, $value); } diff --git a/app/Console/Commands/Location/DeleteLocationCommand.php b/app/Console/Commands/Location/DeleteLocationCommand.php index 898dbb9247..ae9d11915f 100644 --- a/app/Console/Commands/Location/DeleteLocationCommand.php +++ b/app/Console/Commands/Location/DeleteLocationCommand.php @@ -1,56 +1,28 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Location; use Illuminate\Console\Command; +use Illuminate\Support\Collection; use Pterodactyl\Services\Locations\LocationDeletionService; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class DeleteLocationCommand extends Command { - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - protected $deletionService; - - /** - * @var string - */ protected $description = 'Deletes a location from the Panel.'; - /** - * @var \Illuminate\Support\Collection - */ - protected $locations; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - - /** - * @var string - */ protected $signature = 'p:location:delete {--short= : The short code of the location to delete.}'; + protected Collection $locations; + /** * DeleteLocationCommand constructor. */ public function __construct( - LocationDeletionService $deletionService, - LocationRepositoryInterface $repository + private LocationDeletionService $deletionService, + private LocationRepositoryInterface $repository, ) { parent::__construct(); - - $this->deletionService = $deletionService; - $this->repository = $repository; } /** diff --git a/app/Console/Commands/Location/MakeLocationCommand.php b/app/Console/Commands/Location/MakeLocationCommand.php index db4d4e2e0d..f09f1604e8 100644 --- a/app/Console/Commands/Location/MakeLocationCommand.php +++ b/app/Console/Commands/Location/MakeLocationCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Location; @@ -14,31 +7,18 @@ class MakeLocationCommand extends Command { - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - protected $creationService; - - /** - * @var string - */ protected $signature = 'p:location:make {--short= : The shortcode name of this location (ex. us1).} {--long= : A longer description of this location.}'; - /** - * @var string - */ protected $description = 'Creates a new location on the system via the CLI.'; /** * Create a new command instance. */ - public function __construct(LocationCreationService $creationService) + public function __construct(private LocationCreationService $creationService) { parent::__construct(); - - $this->creationService = $creationService; } /** diff --git a/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php index 69a3b118ae..5530014bbc 100644 --- a/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php +++ b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php @@ -2,30 +2,21 @@ namespace Pterodactyl\Console\Commands\Maintenance; -use SplFileInfo; use Carbon\Carbon; use Illuminate\Console\Command; +use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; class CleanServiceBackupFilesCommand extends Command { public const BACKUP_THRESHOLD_MINUTES = 5; - /** - * @var string - */ protected $description = 'Clean orphaned .bak files created when modifying services.'; - /** - * @var \Illuminate\Contracts\Filesystem\Filesystem - */ - protected $disk; - - /** - * @var string - */ protected $signature = 'p:maintenance:clean-service-backups'; + protected Filesystem $disk; + /** * CleanServiceBackupFilesCommand constructor. */ @@ -43,9 +34,9 @@ public function handle() { $files = $this->disk->files('services/.bak'); - collect($files)->each(function (SplFileInfo $file) { + collect($files)->each(function (\SplFileInfo $file) { $lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath())); - if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) { + if ((int) $lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) { $this->disk->delete($file->getPath()); $this->info(trans('command/messages.maintenance.deleting_service_backup', ['file' => $file->getFilename()])); } diff --git a/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php b/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php index 71aeb4f309..b7a04f8eee 100644 --- a/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php +++ b/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php @@ -3,30 +3,31 @@ namespace Pterodactyl\Console\Commands\Maintenance; use Carbon\CarbonImmutable; -use InvalidArgumentException; use Illuminate\Console\Command; use Pterodactyl\Repositories\Eloquent\BackupRepository; class PruneOrphanedBackupsCommand extends Command { - /** - * @var string - */ protected $signature = 'p:maintenance:prune-backups {--prune-age=}'; + protected $description = 'Marks all backups older than "n" minutes that have not yet completed as being failed.'; + /** - * @var string + * PruneOrphanedBackupsCommand constructor. */ - protected $description = 'Marks all backups that have not completed in the last "n" minutes as being failed.'; + public function __construct(private BackupRepository $backupRepository) + { + parent::__construct(); + } - public function handle(BackupRepository $repository) + public function handle() { $since = $this->option('prune-age') ?? config('backups.prune_age', 360); if (!$since || !is_digit($since)) { - throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'); + throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'); } - $query = $repository->getBuilder() + $query = $this->backupRepository->getBuilder() ->whereNull('completed_at') ->where('created_at', '<=', CarbonImmutable::now()->subMinutes($since)->toDateTimeString()); @@ -37,7 +38,7 @@ public function handle(BackupRepository $repository) return; } - $this->warn("Marking {$count} backups that have not been marked as completed in the last {$since} minutes as failed."); + $this->warn("Marking $count uncompleted backups that are older than $since minutes as failed."); $query->update([ 'is_successful' => false, diff --git a/app/Console/Commands/Node/MakeNodeCommand.php b/app/Console/Commands/Node/MakeNodeCommand.php index c70aef35fb..2d2623ad78 100644 --- a/app/Console/Commands/Node/MakeNodeCommand.php +++ b/app/Console/Commands/Node/MakeNodeCommand.php @@ -1,13 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - namespace Pterodactyl\Console\Commands\Node; use Illuminate\Console\Command; @@ -15,14 +7,6 @@ class MakeNodeCommand extends Command { - /** - * @var \Pterodactyl\Services\Nodes\NodeCreationService - */ - protected $creationService; - - /** - * @var string - */ protected $signature = 'p:node:make {--name= : A name to identify the node.} {--description= : A description to identify the node.} @@ -41,36 +25,33 @@ class MakeNodeCommand extends Command {--daemonSFTPPort= : Enter the wings SFTP listening port.} {--daemonBase= : Enter the base folder.}'; - /** - * @var string - */ protected $description = 'Creates a new node on the system via the CLI.'; + /** + * MakeNodeCommand constructor. + */ + public function __construct(private NodeCreationService $creationService) + { + parent::__construct(); + } /** * Handle the command execution process. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(NodeCreationService $creationService) + public function handle() { - $this->creationService = $creationService; - $data['name'] = $this->option('name') ?? $this->ask('Enter a short identifier used to distinguish this node from others'); $data['description'] = $this->option('description') ?? $this->ask('Enter a description to identify the node'); $data['location_id'] = $this->option('locationId') ?? $this->ask('Enter a valid location id'); + $data['scheme'] = $this->option('scheme') ?? $this->anticipate( + 'Please either enter https for SSL or http for a non-ssl connection', + ['https', 'http'], + 'https' + ); $data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node'); - if (!filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { - $this->error('The FQDN or IP address provided does not resolve to a valid IP address.'); - return; - } $data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true); - $data['scheme'] = $this->option('scheme') ?? $this->anticipate('Please either enter https for SSL or http for a non-ssl connection', - ["https","http",],"https"); - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { - $this->error('A fully qualified domain name that resolves to a public IP address is required in order to use SSL for this node.'); - return; - } $data['behind_proxy'] = $this->option('proxy') ?? $this->confirm('Is your FQDN behind a proxy?'); $data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm('Should maintenance mode be enabled?'); $data['memory'] = $this->option('maxMemory') ?? $this->ask('Enter the maximum amount of memory'); diff --git a/app/Console/Commands/Node/NodeConfigurationCommand.php b/app/Console/Commands/Node/NodeConfigurationCommand.php new file mode 100644 index 0000000000..83a25720b1 --- /dev/null +++ b/app/Console/Commands/Node/NodeConfigurationCommand.php @@ -0,0 +1,44 @@ +argument('node')) ? 'id' : 'uuid'; + + /** @var Node $node */ + $node = Node::query()->where($column, $this->argument('node'))->firstOr(function () { + $this->error('The selected node does not exist.'); + + exit(1); + }); + + $format = $this->option('format'); + if (!in_array($format, ['yaml', 'yml', 'json'])) { + $this->error('Invalid format specified. Valid options are "yaml" and "json".'); + + return 1; + } + + if ($format === 'json') { + $this->output->write($node->getJsonConfiguration(true)); + } else { + $this->output->write($node->getYamlConfiguration()); + } + + $this->output->newLine(); + + return 0; + } +} diff --git a/app/Console/Commands/Node/NodeListCommand.php b/app/Console/Commands/Node/NodeListCommand.php new file mode 100644 index 0000000000..718ddd0d2b --- /dev/null +++ b/app/Console/Commands/Node/NodeListCommand.php @@ -0,0 +1,34 @@ +with('location')->get()->map(function (Node $node) { + return [ + 'id' => $node->id, + 'uuid' => $node->uuid, + 'name' => $node->name, + 'location' => $node->location->short, + 'host' => $node->getConnectionAddress(), + ]; + }); + + if ($this->option('format') === 'json') { + $this->output->write($nodes->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } else { + $this->table(['ID', 'UUID', 'Name', 'Location', 'Host'], $nodes->toArray()); + } + + $this->output->newLine(); + + return 0; + } +} diff --git a/app/Console/Commands/Overrides/SeedCommand.php b/app/Console/Commands/Overrides/SeedCommand.php index 5f050e340c..7b7b5edb7b 100644 --- a/app/Console/Commands/Overrides/SeedCommand.php +++ b/app/Console/Commands/Overrides/SeedCommand.php @@ -13,14 +13,14 @@ class SeedCommand extends BaseSeedCommand * Block someone from running this seed command if they have not completed * the migration process. */ - public function handle() + public function handle(): int { if (!$this->hasCompletedMigrations()) { $this->showMigrationWarning(); - return; + return 1; } - parent::handle(); + return parent::handle(); } } diff --git a/app/Console/Commands/Overrides/UpCommand.php b/app/Console/Commands/Overrides/UpCommand.php index 3001edcbe0..0a7caaeb77 100644 --- a/app/Console/Commands/Overrides/UpCommand.php +++ b/app/Console/Commands/Overrides/UpCommand.php @@ -13,14 +13,14 @@ class UpCommand extends BaseUpCommand * Block someone from running this up command if they have not completed * the migration process. */ - public function handle() + public function handle(): int { if (!$this->hasCompletedMigrations()) { $this->showMigrationWarning(); - return; + return 1; } - parent::handle(); + return parent::handle(); } } diff --git a/app/Console/Commands/Schedule/ProcessRunnableCommand.php b/app/Console/Commands/Schedule/ProcessRunnableCommand.php index 9e51feaeb0..70fd4d0c7d 100644 --- a/app/Console/Commands/Schedule/ProcessRunnableCommand.php +++ b/app/Console/Commands/Schedule/ProcessRunnableCommand.php @@ -2,31 +2,26 @@ namespace Pterodactyl\Console\Commands\Schedule; -use Exception; -use Throwable; use Illuminate\Console\Command; use Pterodactyl\Models\Schedule; use Illuminate\Support\Facades\Log; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Schedules\ProcessScheduleService; class ProcessRunnableCommand extends Command { - /** - * @var string - */ protected $signature = 'p:schedule:process'; - /** - * @var string - */ protected $description = 'Process schedules in the database and determine which are ready to run.'; /** * Handle command execution. */ - public function handle() + public function handle(): int { - $schedules = Schedule::query()->with('tasks') + $schedules = Schedule::query() + ->with('tasks') + ->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status')) ->where('is_active', true) ->where('is_processing', false) ->whereRaw('next_run_at <= NOW()') @@ -35,7 +30,7 @@ public function handle() if ($schedules->count() < 1) { $this->line('There are no scheduled tasks for servers that need to be run.'); - return; + return 0; } $bar = $this->output->createProgressBar(count($schedules)); @@ -47,6 +42,8 @@ public function handle() } $this->line(''); + + return 0; } /** @@ -69,10 +66,10 @@ protected function processSchedule(Schedule $schedule) 'schedule' => $schedule->name, 'hash' => $schedule->hashid, ])); - } catch (Throwable | Exception $exception) { + } catch (\Throwable $exception) { Log::error($exception, ['schedule_id' => $schedule->id]); - $this->error("An error was encountered while processing Schedule #{$schedule->id}: " . $exception->getMessage()); + $this->error("An error was encountered while processing Schedule #$schedule->id: " . $exception->getMessage()); } } } diff --git a/app/Console/Commands/Server/BulkPowerActionCommand.php b/app/Console/Commands/Server/BulkPowerActionCommand.php index 01607becea..01a5f3c713 100644 --- a/app/Console/Commands/Server/BulkPowerActionCommand.php +++ b/app/Console/Commands/Server/BulkPowerActionCommand.php @@ -4,6 +4,7 @@ use Pterodactyl\Models\Server; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Factory as ValidatorFactory; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; @@ -11,31 +12,33 @@ class BulkPowerActionCommand extends Command { - /** - * @var string - */ protected $signature = 'p:server:bulk-power {action : The action to perform (start, stop, restart, kill)} {--servers= : A comma separated list of servers.} {--nodes= : A comma separated list of nodes.}'; + protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; + /** - * @var string + * BulkPowerActionCommand constructor. */ - protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; + public function __construct(private DaemonPowerRepository $powerRepository, private ValidatorFactory $validator) + { + parent::__construct(); + } /** * Handle the bulk power request. * - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException */ - public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator) + public function handle() { $action = $this->argument('action'); $nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes')); $servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers')); - $validator = $validator->make([ + $validator = $this->validator->make([ 'action' => $action, 'nodes' => $nodes, 'servers' => $servers, @@ -61,6 +64,7 @@ public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory } $bar = $this->output->createProgressBar($count); + $powerRepository = $this->powerRepository; $this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) { $bar->clear(); @@ -84,10 +88,8 @@ public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory /** * Returns the query builder instance that will return the servers that should be affected. - * - * @return \Illuminate\Database\Eloquent\Builder */ - protected function getQueryBuilder(array $servers, array $nodes) + protected function getQueryBuilder(array $servers, array $nodes): Builder { $instance = Server::query()->whereNull('status'); @@ -95,7 +97,7 @@ protected function getQueryBuilder(array $servers, array $nodes) $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); } elseif (empty($nodes) && !empty($servers)) { $instance->whereIn('id', $servers); - } elseif (!empty($nodes) && empty($servers)) { + } elseif (!empty($nodes) && empty($servers)) { // @phpstan-ignore empty.variable $instance->whereIn('node_id', $nodes); } diff --git a/app/Console/Commands/TelemetryCommand.php b/app/Console/Commands/TelemetryCommand.php new file mode 100644 index 0000000000..3e1b0c2f87 --- /dev/null +++ b/app/Console/Commands/TelemetryCommand.php @@ -0,0 +1,34 @@ +output->info('Collecting telemetry data, this may take a while...'); + + VarDumper::dump($this->telemetryCollectionService->collect()); + } +} diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index 2243298696..735427690c 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Console\Commands; -use Closure; use Illuminate\Console\Command; use Pterodactyl\Console\Kernel; use Symfony\Component\Process\Process; @@ -12,7 +11,6 @@ class UpgradeCommand extends Command { protected const DEFAULT_URL = 'https://github.com/pterodactyl/panel/releases/%s/panel.tar.gz'; - /** @var string */ protected $signature = 'p:upgrade {--user= : The user that PHP runs under. All files will be owned by this user.} {--group= : The group that PHP runs under. All files will be owned by this group.} @@ -20,7 +18,6 @@ class UpgradeCommand extends Command {--release= : A specific Pterodactyl version to download from GitHub. Leave blank to use latest.} {--skip-download : If set no archive will be downloaded.}'; - /** @var string */ protected $description = 'Downloads a new archive for Pterodactyl from GitHub and then executes the normal upgrade commands.'; /** @@ -42,8 +39,8 @@ public function handle() $this->line($this->getUrl()); } - if (version_compare(PHP_VERSION, '7.4.0') < 0) { - $this->error('Cannot execute self-upgrade process. The minimum required PHP version required is 7.4.0, you have [' . PHP_VERSION . '].'); + if (version_compare(PHP_VERSION, '8.2.0', '<')) { + $this->error('Cannot execute self-upgrade process. The minimum required PHP version required is 8.2.0, you have [' . PHP_VERSION . '].'); } $user = 'www-data'; @@ -87,11 +84,12 @@ public function handle() if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) { $this->warn('Upgrade process terminated by user.'); + return; } } - ini_set('output_buffering', 0); + ini_set('output_buffering', '0'); $bar = $this->output->createProgressBar($skipDownload ? 9 : 10); $bar->start(); @@ -135,7 +133,7 @@ public function handle() /** @var \Illuminate\Foundation\Application $app */ $app = require __DIR__ . '/../../../bootstrap/app.php'; - /** @var \Pterodactyl\Console\Kernel $kernel */ + /** @var Kernel $kernel */ $kernel = $app->make(Kernel::class); $kernel->bootstrap(); $this->setLaravel($app); @@ -178,7 +176,7 @@ public function handle() $this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html'); } - protected function withProgress(ProgressBar $bar, Closure $callback) + protected function withProgress(ProgressBar $bar, \Closure $callback) { $bar->clear(); $callback(); diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index 4c847257ec..2b13b48661 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\User; @@ -16,42 +9,19 @@ class DeleteUserCommand extends Command { - /** - * @var \Pterodactyl\Services\Users\UserDeletionService - */ - protected $deletionService; - - /** - * @var string - */ protected $description = 'Deletes a user from the Panel if no servers are attached to their account.'; - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var string - */ protected $signature = 'p:user:delete {--user=}'; /** * DeleteUserCommand constructor. */ - public function __construct(UserDeletionService $deletionService) + public function __construct(private UserDeletionService $deletionService) { parent::__construct(); - - $this->deletionService = $deletionService; } - /** - * @return bool - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function handle() + public function handle(): int { $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); Assert::notEmpty($search, 'Search term should be an email address, got: %s.'); @@ -68,7 +38,7 @@ public function handle() return $this->handle(); } - return false; + return 1; } if ($this->input->isInteractive()) { @@ -85,7 +55,7 @@ public function handle() if (count($results) > 1) { $this->error(trans('command/messages.user.multiple_found')); - return false; + return 1; } $deleteUser = $results->first(); @@ -95,5 +65,7 @@ public function handle() $this->deletionService->handle($deleteUser); $this->info(trans('command/messages.user.deleted')); } + + return 0; } } diff --git a/app/Console/Commands/User/DisableTwoFactorCommand.php b/app/Console/Commands/User/DisableTwoFactorCommand.php index 05e98c045c..052220114c 100644 --- a/app/Console/Commands/User/DisableTwoFactorCommand.php +++ b/app/Console/Commands/User/DisableTwoFactorCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\User; @@ -14,29 +7,16 @@ class DisableTwoFactorCommand extends Command { - /** - * @var string - */ protected $description = 'Disable two-factor authentication for a specific user in the Panel.'; - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var string - */ protected $signature = 'p:user:disable2fa {--email= : The email of the user to disable 2-Factor for.}'; /** * DisableTwoFactorCommand constructor. */ - public function __construct(UserRepositoryInterface $repository) + public function __construct(private UserRepositoryInterface $repository) { parent::__construct(); - - $this->repository = $repository; } /** diff --git a/app/Console/Commands/User/MakeUserCommand.php b/app/Console/Commands/User/MakeUserCommand.php index f981a2f23e..635a956467 100644 --- a/app/Console/Commands/User/MakeUserCommand.php +++ b/app/Console/Commands/User/MakeUserCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\User; @@ -14,29 +7,16 @@ class MakeUserCommand extends Command { - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - protected $creationService; - - /** - * @var string - */ protected $description = 'Creates a user on the system via the CLI.'; - /** - * @var string - */ protected $signature = 'p:user:make {--email=} {--username=} {--name-first=} {--name-last=} {--password=} {--admin=} {--no-password}'; /** * MakeUserCommand constructor. */ - public function __construct(UserCreationService $creationService) + public function __construct(private UserCreationService $creationService) { parent::__construct(); - - $this->creationService = $creationService; } /** diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d5fa25ec4c..56343297c4 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,15 +2,23 @@ namespace Pterodactyl\Console; +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; +use Illuminate\Database\Console\PruneCommand; +use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Pterodactyl\Services\Telemetry\TelemetryCollectionService; +use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand; +use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; +use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; class Kernel extends ConsoleKernel { /** * Register the commands for the application. */ - protected function commands() + protected function commands(): void { $this->load(__DIR__ . '/Commands'); } @@ -18,17 +26,51 @@ protected function commands() /** * Define the application's command schedule. */ - protected function schedule(Schedule $schedule) + protected function schedule(Schedule $schedule): void { + // https://laravel.com/docs/10.x/upgrade#redis-cache-tags + $schedule->command('cache:prune-stale-tags')->hourly(); + // Execute scheduled commands for servers every minute, as if there was a normal cron running. - $schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping(); + $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); + $schedule->command(CleanServiceBackupFilesCommand::class)->daily(); if (config('backups.prune_age')) { // Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted. - $schedule->command('p:maintenance:prune-backups')->everyThirtyMinutes(); + $schedule->command(PruneOrphanedBackupsCommand::class)->everyThirtyMinutes(); + } + + if (config('activity.prune_days')) { + $schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily(); } - // Every day cleanup any internal backups of service files. - $schedule->command('p:maintenance:clean-service-backups')->daily(); + if (config('pterodactyl.telemetry.enabled')) { + $this->registerTelemetry($schedule); + } + } + + /** + * I wonder what this does. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + private function registerTelemetry(Schedule $schedule): void + { + $settingsRepository = app()->make(SettingsRepository::class); + + $uuid = $settingsRepository->get('app:telemetry:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $settingsRepository->set('app:telemetry:uuid', $uuid); + } + + // Calculate a fixed time to run the data push at, this will be the same time every day. + $time = hexdec(str_replace('-', '', substr($uuid, 27))) % 1440; + $hour = floor($time / 60); + $minute = $time % 60; + + // Run the telemetry collector. + $schedule->call(app()->make(TelemetryCollectionService::class))->description('Collect Telemetry')->dailyAt("$hour:$minute"); } } diff --git a/app/Console/RequiresDatabaseMigrations.php b/app/Console/RequiresDatabaseMigrations.php index 0a4ca4944b..2e5ebe6b23 100644 --- a/app/Console/RequiresDatabaseMigrations.php +++ b/app/Console/RequiresDatabaseMigrations.php @@ -33,7 +33,7 @@ protected function hasCompletedMigrations(): bool * them to properly run the migrations rather than ignoring all of the other previous * errors... */ - protected function showMigrationWarning() + protected function showMigrationWarning(): void { $this->getOutput()->writeln(' | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | diff --git a/app/Contracts/Criteria/CriteriaInterface.php b/app/Contracts/Criteria/CriteriaInterface.php index cb9f56d7f1..5edfcdb620 100644 --- a/app/Contracts/Criteria/CriteriaInterface.php +++ b/app/Contracts/Criteria/CriteriaInterface.php @@ -1,24 +1,14 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Criteria; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Repositories\Repository; interface CriteriaInterface { /** * Apply selected criteria to a repository call. - * - * @param \Illuminate\Database\Eloquent\Model $model - * - * @return mixed */ - public function apply($model, Repository $repository); + public function apply(Model $model, Repository $repository): mixed; } diff --git a/app/Contracts/Extensions/HashidsInterface.php b/app/Contracts/Extensions/HashidsInterface.php index 876c2bac47..3fd3dedbbd 100644 --- a/app/Contracts/Extensions/HashidsInterface.php +++ b/app/Contracts/Extensions/HashidsInterface.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Extensions; @@ -16,12 +9,7 @@ interface HashidsInterface extends VendorHashidsInterface /** * Decode an encoded hashid and return the first result. * - * @param string $encoded - * @param null $default - * - * @return mixed - * * @throws \InvalidArgumentException */ - public function decodeFirst($encoded, $default = null); + public function decodeFirst(string $encoded, ?string $default = null): mixed; } diff --git a/app/Contracts/Http/ClientPermissionsRequest.php b/app/Contracts/Http/ClientPermissionsRequest.php index 85eae2259f..a99bca35cf 100644 --- a/app/Contracts/Http/ClientPermissionsRequest.php +++ b/app/Contracts/Http/ClientPermissionsRequest.php @@ -6,7 +6,7 @@ interface ClientPermissionsRequest { /** * Returns the permissions string indicating which permission should be used to - * validate that the authenticated user has permission to perform this action aganist + * validate that the authenticated user has permission to perform this action against * the given resource (server). */ public function permission(): string; diff --git a/app/Contracts/Models/Identifiable.php b/app/Contracts/Models/Identifiable.php new file mode 100644 index 0000000000..dd0b7e7f80 --- /dev/null +++ b/app/Contracts/Models/Identifiable.php @@ -0,0 +1,10 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; diff --git a/app/Contracts/Repository/DatabaseRepositoryInterface.php b/app/Contracts/Repository/DatabaseRepositoryInterface.php index 622072203a..befc2ce4d6 100644 --- a/app/Contracts/Repository/DatabaseRepositoryInterface.php +++ b/app/Contracts/Repository/DatabaseRepositoryInterface.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Contracts\Repository; -use Pterodactyl\Models\Database; use Illuminate\Support\Collection; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -12,10 +11,8 @@ interface DatabaseRepositoryInterface extends RepositoryInterface /** * Set the connection name to execute statements against. - * - * @return $this */ - public function setConnection(string $connection); + public function setConnection(string $connection): self; /** * Return the connection to execute statements against. @@ -23,12 +20,12 @@ public function setConnection(string $connection); public function getConnection(): string; /** - * Return all of the databases belonging to a server. + * Return all the databases belonging to a server. */ public function getDatabasesForServer(int $server): Collection; /** - * Return all of the databases for a given host with the server relationship loaded. + * Return all the databases for a given host with the server relationship loaded. */ public function getDatabasesForHost(int $host, int $count = 25): LengthAwarePaginator; @@ -39,10 +36,8 @@ public function createDatabase(string $database): bool; /** * Create a new database user on a given connection. - * - * @param $max_connections */ - public function createUser(string $username, string $remote, string $password, string $max_connections): bool; + public function createUser(string $username, string $remote, string $password, ?int $max_connections): bool; /** * Give a specific user access to a given database. @@ -61,8 +56,6 @@ public function dropDatabase(string $database): bool; /** * Drop a given user on a specific connection. - * - * @return mixed */ public function dropUser(string $username, string $remote): bool; } diff --git a/app/Contracts/Repository/EggRepositoryInterface.php b/app/Contracts/Repository/EggRepositoryInterface.php index 733e70cfbc..5f2162c4c5 100644 --- a/app/Contracts/Repository/EggRepositoryInterface.php +++ b/app/Contracts/Repository/EggRepositoryInterface.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; @@ -28,13 +21,11 @@ public function getAllWithCopyAttributes(): Collection; /** * Return an egg with the scriptFrom and configFrom relations loaded onto the model. - * - * @param int|string $value */ - public function getWithCopyAttributes($value, string $column = 'id'): Egg; + public function getWithCopyAttributes(int|string $value, string $column = 'id'): Egg; /** - * Return all of the data needed to export a service. + * Return all the data needed to export a service. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Contracts/Repository/EggVariableRepositoryInterface.php b/app/Contracts/Repository/EggVariableRepositoryInterface.php index 6a00955d4c..e200808186 100644 --- a/app/Contracts/Repository/EggVariableRepositoryInterface.php +++ b/app/Contracts/Repository/EggVariableRepositoryInterface.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; diff --git a/app/Contracts/Repository/LocationRepositoryInterface.php b/app/Contracts/Repository/LocationRepositoryInterface.php index d24cee5bdb..066a2e7c65 100644 --- a/app/Contracts/Repository/LocationRepositoryInterface.php +++ b/app/Contracts/Repository/LocationRepositoryInterface.php @@ -13,14 +13,12 @@ interface LocationRepositoryInterface extends RepositoryInterface public function getAllWithDetails(): Collection; /** - * Return all of the available locations with the nodes as a relationship. + * Return all the available locations with the nodes as a relationship. */ public function getAllWithNodes(): Collection; /** - * Return all of the nodes and their respective count of servers for a location. - * - * @return mixed + * Return all the nodes and their respective count of servers for a location. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -29,8 +27,6 @@ public function getWithNodes(int $id): Location; /** * Return a location and the count of nodes in that location. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function getWithNodeCount(int $id): Location; diff --git a/app/Contracts/Repository/NestRepositoryInterface.php b/app/Contracts/Repository/NestRepositoryInterface.php index 1f430dbf8c..9049d16cb2 100644 --- a/app/Contracts/Repository/NestRepositoryInterface.php +++ b/app/Contracts/Repository/NestRepositoryInterface.php @@ -1,37 +1,25 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; use Pterodactyl\Models\Nest; +use Illuminate\Database\Eloquent\Collection; interface NestRepositoryInterface extends RepositoryInterface { /** * Return a nest or all nests with their associated eggs and variables. * - * @param int $id - * - * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getWithEggs(int $id = null); + public function getWithEggs(?int $id = null): Collection|Nest; /** * Return a nest or all nests and the count of eggs and servers for that nest. * - * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getWithCounts(int $id = null); + public function getWithCounts(?int $id = null): Collection|Nest; /** * Return a nest along with its associated eggs and the servers relation on those eggs. diff --git a/app/Contracts/Repository/PermissionRepositoryInterface.php b/app/Contracts/Repository/PermissionRepositoryInterface.php index b35c0e59dd..ad67f8f41d 100644 --- a/app/Contracts/Repository/PermissionRepositoryInterface.php +++ b/app/Contracts/Repository/PermissionRepositoryInterface.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php index e9350d5f0c..5dc0c6655e 100644 --- a/app/Contracts/Repository/RepositoryInterface.php +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -3,87 +3,67 @@ namespace Pterodactyl\Contracts\Repository; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Contracts\Pagination\LengthAwarePaginator; interface RepositoryInterface { /** * Return an identifier or Model object to be used by the repository. - * - * @return string|\Closure|object */ - public function model(); + public function model(): string; /** * Return the model being used for this repository instance. - * - * @return mixed */ - public function getModel(); + public function getModel(): Model; /** * Returns an instance of a query builder. - * - * @return mixed */ - public function getBuilder(); + public function getBuilder(): Builder; /** * Returns the columns to be selected or returned by the query. - * - * @return mixed */ - public function getColumns(); + public function getColumns(): array; /** * An array of columns to filter the response by. - * - * @param array|string $columns - * - * @return $this */ - public function setColumns($columns = ['*']); + public function setColumns(array|string $columns = ['*']): self; /** * Stop repository update functions from returning a fresh * model when changes are committed. - * - * @return $this */ - public function withoutFreshModel(); + public function withoutFreshModel(): self; /** * Return a fresh model with a repository updates a model. - * - * @return $this */ - public function withFreshModel(); + public function withFreshModel(): self; /** - * Set whether or not the repository should return a fresh model + * Set whether the repository should return a fresh model * when changes are committed. - * - * @return $this */ - public function setFreshModel(bool $fresh = true); + public function setFreshModel(bool $fresh = true): self; /** * Create a new model instance and persist it to the database. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function create(array $fields, bool $validate = true, bool $force = false); + public function create(array $fields, bool $validate = true, bool $force = false): mixed; /** * Find a model that has the specific ID passed. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function find(int $id); + public function find(int $id): mixed; /** * Find a model matching an array of where clauses. @@ -93,11 +73,9 @@ public function findWhere(array $fields): Collection; /** * Find and return the first matching instance for the given fields. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function findFirstWhere(array $fields); + public function findFirstWhere(array $fields): mixed; /** * Return a count of records matching the passed arguments. @@ -117,14 +95,10 @@ public function deleteWhere(array $attributes): int; /** * Update a given ID with the passed array of fields. * - * @param int $id - * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update($id, array $fields, bool $validate = true, bool $force = false); + public function update(int $id, array $fields, bool $validate = true, bool $force = false): mixed; /** * Perform a mass update where matching records are updated using whereIn. @@ -135,11 +109,9 @@ public function updateWhereIn(string $column, array $values, array $fields): int /** * Update a record if it exists in the database, otherwise create it. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false); + public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false): mixed; /** * Return all records associated with the given model. diff --git a/app/Contracts/Repository/ScheduleRepositoryInterface.php b/app/Contracts/Repository/ScheduleRepositoryInterface.php index c7bb0ee267..fa9c18b417 100644 --- a/app/Contracts/Repository/ScheduleRepositoryInterface.php +++ b/app/Contracts/Repository/ScheduleRepositoryInterface.php @@ -8,12 +8,12 @@ interface ScheduleRepositoryInterface extends RepositoryInterface { /** - * Return all of the schedules for a given server. + * Return all the schedules for a given server. */ public function findServerSchedules(int $server): Collection; /** - * Return a schedule model with all of the associated tasks as a relationship. + * Return a schedule model with all the associated tasks as a relationship. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index fc3e6ff1a8..1a30f1ec01 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -16,12 +16,12 @@ public function loadEggRelations(Server $server, bool $refresh = false): Server; /** * Return a collection of servers with their associated data for rebuild operations. */ - public function getDataForRebuild(int $server = null, int $node = null): Collection; + public function getDataForRebuild(?int $server = null, ?int $node = null): Collection; /** * Return a collection of servers with their associated data for reinstall operations. */ - public function getDataForReinstall(int $server = null, int $node = null): Collection; + public function getDataForReinstall(?int $server = null, ?int $node = null): Collection; /** * Return a server model and all variables associated with the server. @@ -67,7 +67,7 @@ public function getByUuid(string $uuid): Server; public function isUniqueUuidCombo(string $uuid, string $short): bool; /** - * Returns all of the servers that exist for a given node in a paginated response. + * Returns all the servers that exist for a given node in a paginated response. */ public function loadAllServersForNode(int $node, int $limit): LengthAwarePaginator; } diff --git a/app/Contracts/Repository/SessionRepositoryInterface.php b/app/Contracts/Repository/SessionRepositoryInterface.php index 305752fb41..0a433af716 100644 --- a/app/Contracts/Repository/SessionRepositoryInterface.php +++ b/app/Contracts/Repository/SessionRepositoryInterface.php @@ -7,14 +7,12 @@ interface SessionRepositoryInterface extends RepositoryInterface { /** - * Return all of the active sessions for a user. + * Return all the active sessions for a user. */ public function getUserSessions(int $user): Collection; /** * Delete a session for a given user. - * - * @return int|null */ - public function deleteUserSession(int $user, string $session); + public function deleteUserSession(int $user, string $session): ?int; } diff --git a/app/Contracts/Repository/SettingsRepositoryInterface.php b/app/Contracts/Repository/SettingsRepositoryInterface.php index e5b2bb773b..09fd8f0e8a 100644 --- a/app/Contracts/Repository/SettingsRepositoryInterface.php +++ b/app/Contracts/Repository/SettingsRepositoryInterface.php @@ -10,16 +10,12 @@ interface SettingsRepositoryInterface extends RepositoryInterface * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function set(string $key, string $value = null); + public function set(string $key, ?string $value = null); /** * Retrieve a persistent setting from the database. - * - * @param mixed $default - * - * @return mixed */ - public function get(string $key, $default); + public function get(string $key, mixed $default): mixed; /** * Remove a key from the database cache. diff --git a/app/Contracts/Repository/TaskRepositoryInterface.php b/app/Contracts/Repository/TaskRepositoryInterface.php index 18811ba5c8..0cd980d218 100644 --- a/app/Contracts/Repository/TaskRepositoryInterface.php +++ b/app/Contracts/Repository/TaskRepositoryInterface.php @@ -15,8 +15,6 @@ public function getTaskForJobProcess(int $id): Task; /** * Returns the next task in a schedule. - * - * @return \Pterodactyl\Models\Task|null */ - public function getNextTask(int $schedule, int $index); + public function getNextTask(int $schedule, int $index): ?Task; } diff --git a/app/Enum/JwtScope.php b/app/Enum/JwtScope.php new file mode 100644 index 0000000000..210968977b --- /dev/null +++ b/app/Enum/JwtScope.php @@ -0,0 +1,12 @@ +name}"); + } + + /** + * Returns a middleware that will throttle the specific resource by server. This + * throttle applies to any user making changes to that resource on the specific + * server, it is NOT per-user. + */ + public function middleware(): string + { + return ThrottleRequests::using($this->throttleKey()); + } + + public function limit(): Limit + { + return match($this) { + self::Backup => Limit::perMinutes(15, 3), + self::Database => Limit::perMinute(2), + self::FilePull => Limit::perMinutes(10, 5), + self::Subuser => Limit::perMinutes(15, 10), + self::Websocket => Limit::perMinute(5), + default => Limit::perMinute(2), + }; + } + + public static function boot(): void + { + foreach (self::cases() as $case) { + RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) { + Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class); + + return $case->limit()->by($server->uuid); + }); + } + } +} diff --git a/app/Events/ActivityLogged.php b/app/Events/ActivityLogged.php new file mode 100644 index 0000000000..ca9db79301 --- /dev/null +++ b/app/Events/ActivityLogged.php @@ -0,0 +1,34 @@ +model->event === $event; + } + + public function actor(): ?Model + { + return $this->isSystem() ? null : $this->model->actor; + } + + public function isServerEvent(): bool + { + return Str::startsWith($this->model->event, 'server:'); + } + + public function isSystem(): bool + { + return is_null($this->model->actor_id); + } +} diff --git a/app/Events/Auth/DirectLogin.php b/app/Events/Auth/DirectLogin.php new file mode 100644 index 0000000000..99df4e1008 --- /dev/null +++ b/app/Events/Auth/DirectLogin.php @@ -0,0 +1,13 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Auth; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class FailedCaptcha +class FailedCaptcha extends Event { use SerializesModels; - /** - * The IP that the request originated from. - * - * @var string - */ - public $ip; - - /** - * The domain that was used to try to verify the request with recaptcha api. - * - * @var string - */ - public $domain; - /** * Create a new event instance. - * - * @param string $ip - * @param string $domain */ - public function __construct($ip, $domain) + public function __construct(public string $ip, public string $domain) { - $this->ip = $ip; - $this->domain = $domain; } } diff --git a/app/Events/Auth/FailedPasswordReset.php b/app/Events/Auth/FailedPasswordReset.php index 913bd0a6f9..7a2bc37991 100644 --- a/app/Events/Auth/FailedPasswordReset.php +++ b/app/Events/Auth/FailedPasswordReset.php @@ -1,43 +1,18 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Auth; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class FailedPasswordReset +class FailedPasswordReset extends Event { use SerializesModels; - /** - * The IP that the request originated from. - * - * @var string - */ - public $ip; - - /** - * The email address that was used when the reset request failed. - * - * @var string - */ - public $email; - /** * Create a new event instance. - * - * @param string $ip - * @param string $email */ - public function __construct($ip, $email) + public function __construct(public string $ip, public string $email) { - $this->ip = $ip; - $this->email = $email; } } diff --git a/app/Events/Auth/ProvidedAuthenticationToken.php b/app/Events/Auth/ProvidedAuthenticationToken.php new file mode 100644 index 0000000000..baf363d527 --- /dev/null +++ b/app/Events/Auth/ProvidedAuthenticationToken.php @@ -0,0 +1,13 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Created +class Created extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Creating.php b/app/Events/Server/Creating.php index 457f586ed9..36e4c5ccb6 100644 --- a/app/Events/Server/Creating.php +++ b/app/Events/Server/Creating.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Creating +class Creating extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Deleted.php b/app/Events/Server/Deleted.php index c9f69cab54..04e496185a 100644 --- a/app/Events/Server/Deleted.php +++ b/app/Events/Server/Deleted.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Deleted +class Deleted extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Deleting.php b/app/Events/Server/Deleting.php index e411e2a70d..636b9483c4 100644 --- a/app/Events/Server/Deleting.php +++ b/app/Events/Server/Deleting.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Deleting +class Deleting extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Installed.php b/app/Events/Server/Installed.php index 81b6963bc6..614ead218b 100644 --- a/app/Events/Server/Installed.php +++ b/app/Events/Server/Installed.php @@ -10,18 +10,10 @@ class Installed extends Event { use SerializesModels; - /** - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. - * - * @var \Pterodactyl\Models\Server */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Saved.php b/app/Events/Server/Saved.php index c6dc024c5f..d9b4151eb1 100644 --- a/app/Events/Server/Saved.php +++ b/app/Events/Server/Saved.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Saved +class Saved extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Saving.php b/app/Events/Server/Saving.php index 9e80d62655..f2ee59dfaf 100644 --- a/app/Events/Server/Saving.php +++ b/app/Events/Server/Saving.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Saving +class Saving extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Updated.php b/app/Events/Server/Updated.php index b813b0997f..35677f35db 100644 --- a/app/Events/Server/Updated.php +++ b/app/Events/Server/Updated.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Updated +class Updated extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Server/Updating.php b/app/Events/Server/Updating.php index 4c8ead093c..5e70e3add6 100644 --- a/app/Events/Server/Updating.php +++ b/app/Events/Server/Updating.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Server; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class Updating +class Updating extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Server - */ - public $server; - /** * Create a new event instance. */ - public function __construct(Server $server) + public function __construct(public Server $server) { - $this->server = $server; } } diff --git a/app/Events/Subuser/Created.php b/app/Events/Subuser/Created.php index 2467fe99aa..bd4cdbe677 100644 --- a/app/Events/Subuser/Created.php +++ b/app/Events/Subuser/Created.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Subuser; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Subuser; use Illuminate\Queue\SerializesModels; -class Created +class Created extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Subuser - */ - public $subuser; - /** * Create a new event instance. */ - public function __construct(Subuser $subuser) + public function __construct(public Subuser $subuser) { - $this->subuser = $subuser; } } diff --git a/app/Events/Subuser/Creating.php b/app/Events/Subuser/Creating.php index 03aa5f7fd1..fd1e00eeab 100644 --- a/app/Events/Subuser/Creating.php +++ b/app/Events/Subuser/Creating.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Subuser; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Subuser; use Illuminate\Queue\SerializesModels; -class Creating +class Creating extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Subuser - */ - public $subuser; - /** * Create a new event instance. */ - public function __construct(Subuser $subuser) + public function __construct(public Subuser $subuser) { - $this->subuser = $subuser; } } diff --git a/app/Events/Subuser/Deleted.php b/app/Events/Subuser/Deleted.php index 0e9744c3ff..6c0dc05ec9 100644 --- a/app/Events/Subuser/Deleted.php +++ b/app/Events/Subuser/Deleted.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Subuser; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Subuser; use Illuminate\Queue\SerializesModels; -class Deleted +class Deleted extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Subuser - */ - public $subuser; - /** * Create a new event instance. */ - public function __construct(Subuser $subuser) + public function __construct(public Subuser $subuser) { - $this->subuser = $subuser; } } diff --git a/app/Events/Subuser/Deleting.php b/app/Events/Subuser/Deleting.php index 9d1e12c2f5..d26b3fc44a 100644 --- a/app/Events/Subuser/Deleting.php +++ b/app/Events/Subuser/Deleting.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\Subuser; +use Pterodactyl\Events\Event; use Pterodactyl\Models\Subuser; use Illuminate\Queue\SerializesModels; -class Deleting +class Deleting extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\Subuser - */ - public $subuser; - /** * Create a new event instance. */ - public function __construct(Subuser $subuser) + public function __construct(public Subuser $subuser) { - $this->subuser = $subuser; } } diff --git a/app/Events/User/Created.php b/app/Events/User/Created.php index 72eba811cd..b4e3cf7928 100644 --- a/app/Events/User/Created.php +++ b/app/Events/User/Created.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\User; use Pterodactyl\Models\User; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class Created +class Created extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new event instance. */ - public function __construct(User $user) + public function __construct(public User $user) { - $this->user = $user; } } diff --git a/app/Events/User/Creating.php b/app/Events/User/Creating.php index 7948d70671..75f76c95b8 100644 --- a/app/Events/User/Creating.php +++ b/app/Events/User/Creating.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\User; use Pterodactyl\Models\User; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class Creating +class Creating extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new event instance. */ - public function __construct(User $user) + public function __construct(public User $user) { - $this->user = $user; } } diff --git a/app/Events/User/Deleted.php b/app/Events/User/Deleted.php index 48b5248740..914014cf5d 100644 --- a/app/Events/User/Deleted.php +++ b/app/Events/User/Deleted.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\User; use Pterodactyl\Models\User; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class Deleted +class Deleted extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new event instance. */ - public function __construct(User $user) + public function __construct(public User $user) { - $this->user = $user; } } diff --git a/app/Events/User/Deleting.php b/app/Events/User/Deleting.php index a386bd951a..ba8df8b74a 100644 --- a/app/Events/User/Deleting.php +++ b/app/Events/User/Deleting.php @@ -1,33 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Events\User; use Pterodactyl\Models\User; +use Pterodactyl\Events\Event; use Illuminate\Queue\SerializesModels; -class Deleting +class Deleting extends Event { use SerializesModels; - /** - * The Eloquent model of the server. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new event instance. */ - public function __construct(User $user) + public function __construct(public User $user) { - $this->user = $user; } } diff --git a/app/Events/User/PasswordChanged.php b/app/Events/User/PasswordChanged.php new file mode 100644 index 0000000000..29c6d3a619 --- /dev/null +++ b/app/Events/User/PasswordChanged.php @@ -0,0 +1,15 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions; -use Exception; - -class AccountNotFoundException extends Exception +class AccountNotFoundException extends \Exception { } diff --git a/app/Exceptions/AutoDeploymentException.php b/app/Exceptions/AutoDeploymentException.php index 96627763bc..20405fba21 100644 --- a/app/Exceptions/AutoDeploymentException.php +++ b/app/Exceptions/AutoDeploymentException.php @@ -1,16 +1,7 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions; -use Exception; - -class AutoDeploymentException extends Exception +class AutoDeploymentException extends \Exception { } diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index 063340a726..884df9a893 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -3,13 +3,16 @@ namespace Pterodactyl\Exceptions; use Exception; -use Throwable; +use Illuminate\Http\Request; use Psr\Log\LoggerInterface; use Illuminate\Http\Response; +use Illuminate\Http\JsonResponse; use Illuminate\Container\Container; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class DisplayException extends PterodactylException +class DisplayException extends PterodactylException implements HttpExceptionInterface { public const LEVEL_DEBUG = 'debug'; public const LEVEL_INFO = 'info'; @@ -17,58 +20,40 @@ class DisplayException extends PterodactylException public const LEVEL_ERROR = 'error'; /** - * @var string + * DisplayException constructor. */ - protected $level; - - /** - * Exception constructor. - * - * @param string $message - * @param string $level - * @param int $code - */ - public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0) + public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0) { parent::__construct($message, $code, $previous); - - $this->level = $level; } - /** - * @return string - */ - public function getErrorLevel() + public function getErrorLevel(): string { return $this->level; } - /** - * @return int - */ - public function getStatusCode() + public function getStatusCode(): int { return Response::HTTP_BAD_REQUEST; } + public function getHeaders(): array + { + return []; + } + /** * Render the exception to the user by adding a flashed message to the session * and then redirecting them back to the page that they came from. If the * request originated from an API hit, return the error in JSONAPI spec format. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse */ - public function render($request) + public function render(Request $request): JsonResponse|RedirectResponse { if ($request->expectsJson()) { - return response()->json(Handler::convertToArray($this, [ - 'detail' => $this->getMessage(), - ]), method_exists($this, 'getStatusCode') ? $this->getStatusCode() : Response::HTTP_BAD_REQUEST); + return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); } - Container::getInstance()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); return redirect()->back()->withInput(); } @@ -77,19 +62,17 @@ public function render($request) * Log the exception to the logs using the defined error level only if the previous * exception is set. * - * @return mixed - * - * @throws \Exception + * @throws \Throwable */ public function report() { - if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) { + if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { return null; } try { $logger = Container::getInstance()->make(LoggerInterface::class); - } catch (Exception $ex) { + } catch (\Exception) { throw $this->getPrevious(); } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a94ad8aad2..a757917b72 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,22 +3,22 @@ namespace Pterodactyl\Exceptions; use Exception; -use Throwable; -use PDOException; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use Swift_TransportException; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; use Illuminate\Container\Container; use Illuminate\Database\Connection; +use Illuminate\Http\RedirectResponse; use Illuminate\Foundation\Application; use Illuminate\Auth\AuthenticationException; use Illuminate\Session\TokenMismatchException; use Illuminate\Validation\ValidationException; +use Symfony\Component\HttpFoundation\Response; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Mailer\Exception\TransportException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; @@ -26,7 +26,7 @@ class Handler extends ExceptionHandler { /** - * Laravel's validation parser formats custom rules using the class name + * The validation parser in Laravel formats custom rules using the class name * resulting in some weird rule names. This string will be parsed out and * replaced with 'p_' in the response code. */ @@ -34,8 +34,6 @@ class Handler extends ExceptionHandler /** * A list of the exception types that should not be reported. - * - * @var array */ protected $dontReport = [ AuthenticationException::class, @@ -47,10 +45,18 @@ class Handler extends ExceptionHandler ValidationException::class, ]; + /** + * Maps exceptions to a specific response code. This handles special exception + * types that don't have a defined response code. + */ + protected static array $exceptionResponseCodes = [ + AuthenticationException::class => 401, + AuthorizationException::class => 403, + ValidationException::class => 422, + ]; + /** * A list of the inputs that are never flashed for validation exceptions. - * - * @var array */ protected $dontFlash = [ 'token', @@ -67,22 +73,22 @@ class Handler extends ExceptionHandler * * @noinspection PhpUnusedLocalVariableInspection */ - public function register() + public function register(): void { if (config('app.exceptions.report_all', false)) { $this->dontReport = []; } - $this->reportable(function (PDOException $ex) { + $this->reportable(function (\PDOException $ex) { $ex = $this->generateCleanedExceptionStack($ex); }); - $this->reportable(function (Swift_TransportException $ex) { + $this->reportable(function (TransportException $ex) { $ex = $this->generateCleanedExceptionStack($ex); }); } - private function generateCleanedExceptionStack(Throwable $exception): string + private function generateCleanedExceptionStack(\Throwable $exception): string { $cleanedStack = ''; foreach ($exception->getTrace() as $index => $item) { @@ -113,11 +119,9 @@ class_basename($exception), * * @param \Illuminate\Http\Request $request * - * @return \Symfony\Component\HttpFoundation\Response - * * @throws \Throwable */ - public function render($request, Throwable $exception) + public function render($request, \Throwable $e): Response { $connections = $this->container->make(Connection::class); @@ -134,7 +138,7 @@ public function render($request, Throwable $exception) $connections->rollBack(0); } - return parent::render($request, $exception); + return parent::render($request, $e); } /** @@ -142,10 +146,8 @@ public function render($request, Throwable $exception) * calls to the API. * * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse */ - public function invalidJson($request, ValidationException $exception) + public function invalidJson($request, ValidationException $exception): JsonResponse { $codes = Collection::make($exception->validator->failed())->mapWithKeys(function ($reasons, $field) { $cleaned = []; @@ -167,9 +169,9 @@ public function invalidJson($request, ValidationException $exception) )), ]; - $converted = self::convertToArray($exception)['errors'][0]; + $converted = $this->convertExceptionToArray($exception)['errors'][0]; $converted['detail'] = $error; - $converted['meta'] = is_array($converted['meta'] ?? null) ? array_merge($converted['meta'], $meta) : $meta; + $converted['meta'] = array_merge($converted['meta'] ?? [], $meta); $response[] = $converted; } @@ -185,19 +187,21 @@ public function invalidJson($request, ValidationException $exception) /** * Return the exception as a JSONAPI representation for use on API requests. */ - public static function convertToArray(Throwable $exception, array $override = []): array + protected function convertExceptionToArray(\Throwable $e, array $override = []): array { + $match = self::$exceptionResponseCodes[get_class($e)] ?? null; + $error = [ - 'code' => class_basename($exception), - 'status' => method_exists($exception, 'getStatusCode') - ? strval($exception->getStatusCode()) - : ($exception instanceof ValidationException ? '422' : '500'), - 'detail' => $exception instanceof HttpExceptionInterface - ? $exception->getMessage() + 'code' => class_basename($e), + 'status' => method_exists($e, 'getStatusCode') + ? strval($e->getStatusCode()) + : strval($match ?? '500'), + 'detail' => $e instanceof HttpExceptionInterface || !is_null($match) + ? $e->getMessage() : 'An unexpected error was encountered while processing this request, please try again.', ]; - if ($exception instanceof ModelNotFoundException || $exception->getPrevious() instanceof ModelNotFoundException) { + if ($e instanceof ModelNotFoundException || $e->getPrevious() instanceof ModelNotFoundException) { // Show a nicer error message compared to the standard "No query results for model" // response that is normally returned. If we are in debug mode this will get overwritten // with a more specific error message to help narrow down things. @@ -206,13 +210,19 @@ public static function convertToArray(Throwable $exception, array $override = [] if (config('app.debug')) { $error = array_merge($error, [ - 'detail' => $exception->getMessage(), + 'detail' => $e->getMessage(), 'source' => [ - 'line' => $exception->getLine(), - 'file' => str_replace(Application::getInstance()->basePath(), '', $exception->getFile()), + 'line' => $e->getLine(), + 'file' => str_replace(Application::getInstance()->basePath(), '', $e->getFile()), ], 'meta' => [ - 'trace' => explode("\n", $exception->getTraceAsString()), + 'trace' => Collection::make($e->getTrace()) + ->map(fn ($trace) => Arr::except($trace, ['args'])) + ->all(), + 'previous' => Collection::make($this->extractPrevious($e)) + ->map(fn ($exception) => $e->getTrace()) + ->map(fn ($trace) => Arr::except($trace, ['args'])) + ->all(), ], ]); } @@ -223,35 +233,51 @@ public static function convertToArray(Throwable $exception, array $override = [] /** * Return an array of exceptions that should not be reported. */ - public static function isReportable(Exception $exception): bool + public static function isReportable(\Exception $exception): bool { - return (new static(Container::getInstance()))->shouldReport($exception); + return (new self(Container::getInstance()))->shouldReport($exception); } /** * Convert an authentication exception into an unauthenticated response. * * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse */ - protected function unauthenticated($request, AuthenticationException $exception) + protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse { if ($request->expectsJson()) { - return new JsonResponse(self::convertToArray($exception), JsonResponse::HTTP_UNAUTHORIZED); + return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED); } - return $this->container->make('redirect')->guest('/auth/login'); + return redirect()->guest('/auth/login'); } /** - * Converts an exception into an array to render in the response. Overrides - * Laravel's built-in converter to output as a JSONAPI spec compliant object. + * Extracts all the previous exceptions that lead to the one passed into this + * function being thrown. * - * @return array + * @return \Throwable[] + */ + protected function extractPrevious(\Throwable $e): array + { + $previous = []; + while ($value = $e->getPrevious()) { + if (!$value instanceof \Throwable) { // @phpstan-ignore instanceof.alwaysTrue + break; + } + $previous[] = $value; + $e = $value; + } + + return $previous; + } + + /** + * Helper method to allow reaching into the handler to convert an exception + * into the expected array response type. */ - protected function convertExceptionToArray(Throwable $exception) + public static function toArray(\Throwable $e): array { - return self::convertToArray($exception); + return (new self(app()))->convertExceptionToArray($e); } } diff --git a/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php index 5f17186bc5..bdcb8ec349 100644 --- a/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php +++ b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Http\Base; diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php index c308b9b72f..8c3d474a63 100644 --- a/app/Exceptions/Http/Connection/DaemonConnectionException.php +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -12,20 +12,15 @@ */ class DaemonConnectionException extends DisplayException { - /** - * @var int - */ - private $statusCode = Response::HTTP_GATEWAY_TIMEOUT; + private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT; /** * Every request to the Wings instance will return a unique X-Request-Id header * which allows for all errors to be efficiently tied to a specific request that * triggered them, and gives users a more direct method of informing hosts when * something goes wrong. - * - * @var string|null */ - private $requestId; + private ?string $requestId; /** * Throw a displayable exception caused by a daemon connection error. @@ -34,7 +29,7 @@ public function __construct(GuzzleException $previous, bool $useStatusCode = tru { /** @var \GuzzleHttp\Psr7\Response|null $response */ $response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null; - $this->requestId = $response ? $response->getHeaderLine('X-Request-Id') : null; + $this->requestId = $response?->getHeaderLine('X-Request-Id'); if ($useStatusCode) { $this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode(); @@ -72,8 +67,6 @@ public function __construct(GuzzleException $previous, bool $useStatusCode = tru /** * Override the default reporting method for DisplayException by just logging immediately * here and including the specific X-Request-Id header that was returned by the call. - * - * @return void */ public function report() { @@ -84,18 +77,13 @@ public function report() /** * Return the HTTP status code for this exception. - * - * @return int */ - public function getStatusCode() + public function getStatusCode(): int { return $this->statusCode; } - /** - * @return string|null - */ - public function getRequestId() + public function getRequestId(): ?string { return $this->requestId; } diff --git a/app/Exceptions/Http/HttpForbiddenException.php b/app/Exceptions/Http/HttpForbiddenException.php index 1488265a93..3fc8cb6d8c 100644 --- a/app/Exceptions/Http/HttpForbiddenException.php +++ b/app/Exceptions/Http/HttpForbiddenException.php @@ -10,7 +10,7 @@ class HttpForbiddenException extends HttpException /** * HttpForbiddenException constructor. */ - public function __construct(string $message = null, \Throwable $previous = null) + public function __construct(?string $message = null, ?\Throwable $previous = null) { parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous); } diff --git a/app/Exceptions/Http/Server/FileTypeNotEditableException.php b/app/Exceptions/Http/Server/FileTypeNotEditableException.php index c47dd3695a..06f6a508b9 100644 --- a/app/Exceptions/Http/Server/FileTypeNotEditableException.php +++ b/app/Exceptions/Http/Server/FileTypeNotEditableException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Http\Server; diff --git a/app/Exceptions/Http/Server/ServerStateConflictException.php b/app/Exceptions/Http/Server/ServerStateConflictException.php index f0eb096b16..7d19bfc497 100644 --- a/app/Exceptions/Http/Server/ServerStateConflictException.php +++ b/app/Exceptions/Http/Server/ServerStateConflictException.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Exceptions\Http\Server; -use Throwable; use Pterodactyl\Models\Server; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; @@ -12,11 +11,13 @@ class ServerStateConflictException extends ConflictHttpException * Exception thrown when the server is in an unsupported state for API access or * certain operations within the codebase. */ - public function __construct(Server $server, Throwable $previous = null) + public function __construct(Server $server, ?\Throwable $previous = null) { $message = 'This server is currently in an unsupported state, please try again later.'; if ($server->isSuspended()) { $message = 'This server is currently suspended and the functionality requested is unavailable.'; + } elseif ($server->node->isUnderMaintenance()) { + $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.'; } elseif (!$server->isInstalled()) { $message = 'This server has not yet completed its installation process, please try again later.'; } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) { diff --git a/app/Exceptions/Http/TwoFactorAuthRequiredException.php b/app/Exceptions/Http/TwoFactorAuthRequiredException.php index cecb646265..9689338c2e 100644 --- a/app/Exceptions/Http/TwoFactorAuthRequiredException.php +++ b/app/Exceptions/Http/TwoFactorAuthRequiredException.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Exceptions\Http; -use Throwable; use Illuminate\Http\Response; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; @@ -12,7 +11,7 @@ class TwoFactorAuthRequiredException extends HttpException implements HttpExcept /** * TwoFactorAuthRequiredException constructor. */ - public function __construct(Throwable $previous = null) + public function __construct(?\Throwable $previous = null) { parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous); } diff --git a/app/Exceptions/ManifestDoesNotExistException.php b/app/Exceptions/ManifestDoesNotExistException.php new file mode 100644 index 0000000000..206af362df --- /dev/null +++ b/app/Exceptions/ManifestDoesNotExistException.php @@ -0,0 +1,14 @@ +errors()->toJson() + $message = sprintf( + 'Could not save %s[%s]: failed to validate data: %s', + get_class($model), + $model->getKey(), + $validator->errors()->toJson() ); - $this->validator = $validator; + parent::__construct($message); } /** * Return the validator message bag. - * - * @return \Illuminate\Support\MessageBag */ - public function getMessageBag() + public function getMessageBag(): MessageBag { return $this->validator->errors(); } /** * Return the status code for this request. - * - * @return int */ - public function getStatusCode() + public function getStatusCode(): int { return 500; } - /** - * @return array - */ - public function getHeaders() + public function getHeaders(): array { return []; } + + public function getValidator(): Validator + { + return $this->validator; + } + + public function getModel(): Model + { + return $this->model; + } } diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php index 51766a9242..451ae92cbc 100644 --- a/app/Exceptions/PterodactylException.php +++ b/app/Exceptions/PterodactylException.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Exceptions; -use Exception; - -class PterodactylException extends Exception +class PterodactylException extends \Exception { } diff --git a/app/Exceptions/Repository/RecordNotFoundException.php b/app/Exceptions/Repository/RecordNotFoundException.php index 53cfa22228..ed1127ce75 100644 --- a/app/Exceptions/Repository/RecordNotFoundException.php +++ b/app/Exceptions/Repository/RecordNotFoundException.php @@ -9,20 +9,16 @@ class RecordNotFoundException extends RepositoryException implements HttpExcepti { /** * Returns the status code. - * - * @return int */ - public function getStatusCode() + public function getStatusCode(): int { return Response::HTTP_NOT_FOUND; } /** * Returns response headers. - * - * @return array */ - public function getHeaders() + public function getHeaders(): array { return []; } diff --git a/app/Exceptions/Service/Allocation/InvalidPortMappingException.php b/app/Exceptions/Service/Allocation/InvalidPortMappingException.php index fe14413eb2..6cf066a5cd 100644 --- a/app/Exceptions/Service/Allocation/InvalidPortMappingException.php +++ b/app/Exceptions/Service/Allocation/InvalidPortMappingException.php @@ -8,10 +8,8 @@ class InvalidPortMappingException extends DisplayException { /** * InvalidPortMappingException constructor. - * - * @param mixed $port */ - public function __construct($port) + public function __construct(mixed $port) { parent::__construct(trans('exceptions.allocations.invalid_mapping', ['port' => $port])); } diff --git a/app/Exceptions/Service/Egg/HasChildrenException.php b/app/Exceptions/Service/Egg/HasChildrenException.php index 7198f83063..9463b70e44 100644 --- a/app/Exceptions/Service/Egg/HasChildrenException.php +++ b/app/Exceptions/Service/Egg/HasChildrenException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Egg; diff --git a/app/Exceptions/Service/Egg/InvalidCopyFromException.php b/app/Exceptions/Service/Egg/InvalidCopyFromException.php index 149c42dd6c..d0e33e21f2 100644 --- a/app/Exceptions/Service/Egg/InvalidCopyFromException.php +++ b/app/Exceptions/Service/Egg/InvalidCopyFromException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Egg; diff --git a/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php b/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php index 867b09c1a8..ab9f5b4046 100644 --- a/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php +++ b/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Egg; diff --git a/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php b/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php index 03ad09e5ec..bc921ee25d 100644 --- a/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php +++ b/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Egg\Variable; diff --git a/app/Exceptions/Service/HasActiveServersException.php b/app/Exceptions/Service/HasActiveServersException.php index 5c43101c54..d3b46a8841 100644 --- a/app/Exceptions/Service/HasActiveServersException.php +++ b/app/Exceptions/Service/HasActiveServersException.php @@ -7,10 +7,7 @@ class HasActiveServersException extends DisplayException { - /** - * @return int - */ - public function getStatusCode() + public function getStatusCode(): int { return Response::HTTP_BAD_REQUEST; } diff --git a/app/Exceptions/Service/Helper/CdnVersionFetchingException.php b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php index 2f934033aa..e085d36350 100644 --- a/app/Exceptions/Service/Helper/CdnVersionFetchingException.php +++ b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php @@ -1,16 +1,7 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Helper; -use Exception; - -class CdnVersionFetchingException extends Exception +class CdnVersionFetchingException extends \Exception { } diff --git a/app/Exceptions/Service/InvalidFileUploadException.php b/app/Exceptions/Service/InvalidFileUploadException.php index 6d0585d2ac..7d73ac207c 100644 --- a/app/Exceptions/Service/InvalidFileUploadException.php +++ b/app/Exceptions/Service/InvalidFileUploadException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service; diff --git a/app/Exceptions/Service/Location/HasActiveNodesException.php b/app/Exceptions/Service/Location/HasActiveNodesException.php index 1270807b8d..43a4e01e31 100644 --- a/app/Exceptions/Service/Location/HasActiveNodesException.php +++ b/app/Exceptions/Service/Location/HasActiveNodesException.php @@ -7,10 +7,7 @@ class HasActiveNodesException extends DisplayException { - /** - * @return int - */ - public function getStatusCode() + public function getStatusCode(): int { return Response::HTTP_BAD_REQUEST; } diff --git a/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php b/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php index 1863ce7eea..0c3afb48cf 100644 --- a/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php +++ b/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Schedule\Task; diff --git a/app/Exceptions/Service/Server/RequiredVariableMissingException.php b/app/Exceptions/Service/Server/RequiredVariableMissingException.php index 068c8af0f7..e8423a4284 100644 --- a/app/Exceptions/Service/Server/RequiredVariableMissingException.php +++ b/app/Exceptions/Service/Server/RequiredVariableMissingException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Server; diff --git a/app/Exceptions/Service/ServiceLimitExceededException.php b/app/Exceptions/Service/ServiceLimitExceededException.php index 7236034feb..9bbb371f4c 100644 --- a/app/Exceptions/Service/ServiceLimitExceededException.php +++ b/app/Exceptions/Service/ServiceLimitExceededException.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Exceptions\Service; -use Throwable; use Pterodactyl\Exceptions\DisplayException; class ServiceLimitExceededException extends DisplayException @@ -11,7 +10,7 @@ class ServiceLimitExceededException extends DisplayException * Exception thrown when something goes over a defined limit, such as allocated * ports, tasks, databases, etc. */ - public function __construct(string $message, Throwable $previous = null) + public function __construct(string $message, ?\Throwable $previous = null) { parent::__construct($message, $previous, self::LEVEL_WARNING); } diff --git a/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php index 87c845927e..8133a13345 100644 --- a/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php +++ b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Subuser; diff --git a/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php index 99839424ef..cc7225a2bd 100644 --- a/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php +++ b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\Subuser; diff --git a/app/Exceptions/Solutions/ManifestDoesNotExistSolution.php b/app/Exceptions/Solutions/ManifestDoesNotExistSolution.php new file mode 100644 index 0000000000..7ef35fe636 --- /dev/null +++ b/app/Exceptions/Solutions/ManifestDoesNotExistSolution.php @@ -0,0 +1,25 @@ + 'https://github.com/pterodactyl/panel/blob/develop/package.json', + ]; + } +} diff --git a/app/Extensions/Backups/BackupManager.php b/app/Extensions/Backups/BackupManager.php index 80515848e0..60f394fb92 100644 --- a/app/Extensions/Backups/BackupManager.php +++ b/app/Extensions/Backups/BackupManager.php @@ -7,66 +7,46 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; use Webmozart\Assert\Assert; -use InvalidArgumentException; use Illuminate\Foundation\Application; -use League\Flysystem\AdapterInterface; -use League\Flysystem\AwsS3v3\AwsS3Adapter; -use League\Flysystem\Memory\MemoryAdapter; -use Illuminate\Contracts\Config\Repository; +use League\Flysystem\FilesystemAdapter; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; +use League\Flysystem\InMemory\InMemoryFilesystemAdapter; +use Illuminate\Contracts\Config\Repository as ConfigRepository; class BackupManager { - /** - * @var \Illuminate\Foundation\Application - */ - protected $app; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; + protected ConfigRepository $config; /** * The array of resolved backup drivers. - * - * @var \League\Flysystem\AdapterInterface[] */ - protected $adapters = []; + protected array $adapters = []; /** * The registered custom driver creators. - * - * @var array */ - protected $customCreators; + protected array $customCreators; /** * BackupManager constructor. */ - public function __construct(Application $app) + public function __construct(protected Application $app) { - $this->app = $app; - $this->config = $app->make(Repository::class); + $this->config = $app->make(ConfigRepository::class); } /** * Returns a backup adapter instance. - * - * @return \League\Flysystem\AdapterInterface */ - public function adapter(string $name = null) + public function adapter(?string $name = null): FilesystemAdapter { return $this->get($name ?: $this->getDefaultAdapter()); } /** * Set the given backup adapter instance. - * - * @param \League\Flysystem\AdapterInterface $disk - * - * @return $this */ - public function set(string $name, $disk) + public function set(string $name, FilesystemAdapter $disk): self { $this->adapters[$name] = $disk; @@ -75,25 +55,21 @@ public function set(string $name, $disk) /** * Gets a backup adapter. - * - * @return \League\Flysystem\AdapterInterface */ - protected function get(string $name) + protected function get(string $name): FilesystemAdapter { return $this->adapters[$name] = $this->resolve($name); } /** * Resolve the given backup disk. - * - * @return \League\Flysystem\AdapterInterface */ - protected function resolve(string $name) + protected function resolve(string $name): FilesystemAdapter { $config = $this->getConfig($name); if (empty($config['adapter'])) { - throw new InvalidArgumentException("Backup disk [{$name}] does not have a configured adapter."); + throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter."); } $adapter = $config['adapter']; @@ -106,44 +82,34 @@ protected function resolve(string $name) if (method_exists($this, $adapterMethod)) { $instance = $this->{$adapterMethod}($config); - Assert::isInstanceOf($instance, AdapterInterface::class); + Assert::isInstanceOf($instance, FilesystemAdapter::class); return $instance; } - throw new InvalidArgumentException("Adapter [{$adapter}] is not supported."); + throw new \InvalidArgumentException("Adapter [$adapter] is not supported."); } /** * Calls a custom creator for a given adapter type. - * - * @return \League\Flysystem\AdapterInterface */ - protected function callCustomCreator(array $config) + protected function callCustomCreator(array $config): mixed { - $adapter = $this->customCreators[$config['adapter']]($this->app, $config); - - Assert::isInstanceOf($adapter, AdapterInterface::class); - - return $adapter; + return $this->customCreators[$config['adapter']]($this->app, $config); } /** - * Creates a new wings adapter. - * - * @return \League\Flysystem\AdapterInterface + * Creates a new Wings adapter. */ - public function createWingsAdapter(array $config) + public function createWingsAdapter(array $config): FilesystemAdapter { - return new MemoryAdapter(null); + return new InMemoryFilesystemAdapter(); } /** * Creates a new S3 adapter. - * - * @return \League\Flysystem\AdapterInterface */ - public function createS3Adapter(array $config) + public function createS3Adapter(array $config): FilesystemAdapter { $config['version'] = 'latest'; @@ -153,25 +119,21 @@ public function createS3Adapter(array $config) $client = new S3Client($config); - return new AwsS3Adapter($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []); + return new S3Filesystem($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []); } /** * Returns the configuration associated with a given backup type. - * - * @return array */ - protected function getConfig(string $name) + protected function getConfig(string $name): array { - return $this->config->get("backups.disks.{$name}") ?: []; + return $this->config->get("backups.disks.$name") ?: []; } /** * Get the default backup driver name. - * - * @return string */ - public function getDefaultAdapter() + public function getDefaultAdapter(): string { return $this->config->get('backups.default'); } @@ -179,7 +141,7 @@ public function getDefaultAdapter() /** * Set the default session driver name. */ - public function setDefaultAdapter(string $name) + public function setDefaultAdapter(string $name): void { $this->config->set('backups.default', $name); } @@ -188,13 +150,11 @@ public function setDefaultAdapter(string $name) * Unset the given adapter instances. * * @param string|string[] $adapter - * - * @return $this */ - public function forget($adapter) + public function forget(array|string $adapter): self { foreach ((array) $adapter as $adapterName) { - unset($this->adapters[$adapter]); + unset($this->adapters[$adapterName]); } return $this; @@ -202,10 +162,8 @@ public function forget($adapter) /** * Register a custom adapter creator closure. - * - * @return $this */ - public function extend(string $adapter, Closure $callback) + public function extend(string $adapter, \Closure $callback): self { $this->customCreators[$adapter] = $callback; diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php index 94251bce8a..2bed3436e0 100644 --- a/app/Extensions/DynamicDatabaseConnection.php +++ b/app/Extensions/DynamicDatabaseConnection.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Extensions; @@ -20,44 +13,22 @@ class DynamicDatabaseConnection public const DB_COLLATION = 'utf8_unicode_ci'; public const DB_DRIVER = 'mysql'; - /** - * @var \Illuminate\Config\Repository - */ - protected $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - protected $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - protected $repository; - /** * DynamicDatabaseConnection constructor. */ public function __construct( - ConfigRepository $config, - DatabaseHostRepositoryInterface $repository, - Encrypter $encrypter + protected ConfigRepository $config, + protected Encrypter $encrypter, + protected DatabaseHostRepositoryInterface $repository, ) { - $this->config = $config; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Adds a dynamic database connection entry to the runtime config. * - * @param string $connection - * @param \Pterodactyl\Models\DatabaseHost|int $host - * @param string $database - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function set($connection, $host, $database = 'mysql') + public function set(string $connection, DatabaseHost|int $host, string $database = 'mysql'): void { if (!$host instanceof DatabaseHost) { $host = $this->repository->find($host); diff --git a/app/Extensions/Facades/Theme.php b/app/Extensions/Facades/Theme.php index 69e8e0dac2..55ebc1002a 100644 --- a/app/Extensions/Facades/Theme.php +++ b/app/Extensions/Facades/Theme.php @@ -6,10 +6,7 @@ class Theme extends Facade { - /** - * @return string - */ - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): string { return 'extensions.themes'; } diff --git a/app/Extensions/Filesystem/S3Filesystem.php b/app/Extensions/Filesystem/S3Filesystem.php new file mode 100644 index 0000000000..df57a508bf --- /dev/null +++ b/app/Extensions/Filesystem/S3Filesystem.php @@ -0,0 +1,35 @@ +client; + } + + public function getBucket(): string + { + return $this->bucket; + } +} diff --git a/app/Extensions/Hashids.php b/app/Extensions/Hashids.php index ab07fc86dd..8c18ada5c3 100644 --- a/app/Extensions/Hashids.php +++ b/app/Extensions/Hashids.php @@ -1,29 +1,17 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Extensions; +use Illuminate\Support\Arr; use Hashids\Hashids as VendorHashids; use Pterodactyl\Contracts\Extensions\HashidsInterface; class Hashids extends VendorHashids implements HashidsInterface { - /** - * {@inheritdoc} - */ - public function decodeFirst($encoded, $default = null) + public function decodeFirst(string $encoded, ?string $default = null): mixed { $result = $this->decode($encoded); - if (!is_array($result)) { - return $default; - } - return array_first($result, null, $default); + return Arr::first($result, null, $default); } } diff --git a/app/Extensions/Illuminate/Database/Eloquent/Builder.php b/app/Extensions/Illuminate/Database/Eloquent/Builder.php index 0a152eb261..6ce63c2b60 100644 --- a/app/Extensions/Illuminate/Database/Eloquent/Builder.php +++ b/app/Extensions/Illuminate/Database/Eloquent/Builder.php @@ -8,10 +8,8 @@ class Builder extends EloquentBuilder { /** * Do nothing. - * - * @return $this */ - public function search() + public function search(): self { return $this; } diff --git a/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php b/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php new file mode 100644 index 0000000000..9d78252c99 --- /dev/null +++ b/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php @@ -0,0 +1,10 @@ +accessToken = $accessToken; + $this->plainTextToken = $plainTextToken; + } +} diff --git a/app/Extensions/Lcobucci/JWT/Encoding/TimestampDates.php b/app/Extensions/Lcobucci/JWT/Encoding/TimestampDates.php index e13133c34f..e47a3fda00 100644 --- a/app/Extensions/Lcobucci/JWT/Encoding/TimestampDates.php +++ b/app/Extensions/Lcobucci/JWT/Encoding/TimestampDates.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Extensions\Lcobucci\JWT\Encoding; -use DateTimeImmutable; use Lcobucci\JWT\ClaimsFormatter; use Lcobucci\JWT\Token\RegisteredClaims; @@ -21,7 +20,7 @@ public function formatClaims(array $claims): array continue; } - assert($claims[$claim] instanceof DateTimeImmutable); + assert($claims[$claim] instanceof \DateTimeImmutable); $claims[$claim] = $claims[$claim]->getTimestamp(); } diff --git a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php index 2481cf96e4..5b53a5ad70 100644 --- a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php +++ b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php @@ -8,12 +8,8 @@ class PterodactylSerializer extends ArraySerializer { /** * Serialize an item. - * - * @param string $resourceKey - * - * @return array */ - public function item($resourceKey, array $data) + public function item(?string $resourceKey, array $data): array { return [ 'object' => $resourceKey, @@ -23,12 +19,8 @@ public function item($resourceKey, array $data) /** * Serialize a collection. - * - * @param string $resourceKey - * - * @return array */ - public function collection($resourceKey, array $data) + public function collection(?string $resourceKey, array $data): array { $response = []; foreach ($data as $datum) { @@ -43,10 +35,8 @@ public function collection($resourceKey, array $data) /** * Serialize a null resource. - * - * @return array */ - public function null() + public function null(): ?array { return [ 'object' => 'null_resource', @@ -56,13 +46,8 @@ public function null() /** * Merge the included resources with the parent resource being serialized. - * - * @param array $transformedData - * @param array $includedData - * - * @return array */ - public function mergeIncludes($transformedData, $includedData) + public function mergeIncludes(array $transformedData, array $includedData): array { foreach ($includedData as $key => $datum) { $transformedData['relationships'][$key] = $datum; diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index b9c651d353..0c65d6e8e4 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Extensions\Spatie\Fractalistic; +use League\Fractal\Scope; use League\Fractal\TransformerAbstract; use Spatie\Fractal\Fractal as SpatieFractal; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -13,12 +14,10 @@ class Fractal extends SpatieFractal /** * Create fractal data. * - * @return \League\Fractal\Scope - * * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified */ - public function createData() + public function createData(): Scope { // Set the serializer by default. if (is_null($this->serializer)) { diff --git a/app/Extensions/Themes/Theme.php b/app/Extensions/Themes/Theme.php index c82c5f757f..fd65583e32 100644 --- a/app/Extensions/Themes/Theme.php +++ b/app/Extensions/Themes/Theme.php @@ -4,17 +4,17 @@ class Theme { - public function js($path) + public function js($path): string { return sprintf('' . PHP_EOL, $this->getUrl($path)); } - public function css($path) + public function css($path): string { return sprintf('' . PHP_EOL, $this->getUrl($path)); } - protected function getUrl($path) + protected function getUrl($path): string { return '/themes/pterodactyl/' . ltrim($path, '/'); } diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php new file mode 100644 index 0000000000..5640210011 --- /dev/null +++ b/app/Facades/Activity.php @@ -0,0 +1,14 @@ +getTimezone()->getOffset(CarbonImmutable::now('UTC')) / 3600); - - return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad(abs($offset), 2, '0', STR_PAD_LEFT)); + return CarbonImmutable::now($timezone)->getTimezone()->toOffsetName(); } } diff --git a/app/Helpers/Utilities.php b/app/Helpers/Utilities.php index b48e9f0916..0c468e0c70 100644 --- a/app/Helpers/Utilities.php +++ b/app/Helpers/Utilities.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Helpers; -use Exception; use Carbon\Carbon; use Cron\CronExpression; use Illuminate\Support\Facades\Log; @@ -12,7 +11,7 @@ class Utilities { /** * Generates a random string and injects special characters into it, in addition to - * the randomness of the alpha-numeric default response. + * the randomness of the alphanumeric default response. */ public static function randomStringWithSpecialCharacters(int $length = 16): string { @@ -25,7 +24,7 @@ public static function randomStringWithSpecialCharacters(int $length = 16): stri $string = substr_replace($string, $character, random_int(0, $length - 1), 1); } - } catch (Exception $exception) { + } catch (\Exception $exception) { // Just log the error and hope for the best at this point. Log::error($exception); } @@ -36,21 +35,16 @@ public static function randomStringWithSpecialCharacters(int $length = 16): stri /** * Converts schedule cron data into a carbon object. * - * @return \Carbon\Carbon + * @throws \Exception */ - public static function getScheduleNextRunDate(string $minute, string $hour, string $dayOfMonth, string $month, string $dayOfWeek) + public static function getScheduleNextRunDate(string $minute, string $hour, string $dayOfMonth, string $month, string $dayOfWeek): Carbon { - return Carbon::instance(CronExpression::factory( + return Carbon::instance((new CronExpression( sprintf('%s %s %s %s %s', $minute, $hour, $dayOfMonth, $month, $dayOfWeek) - )->getNextRunDate()); + ))->getNextRunDate()); } - /** - * @param mixed $default - * - * @return string - */ - public static function checked(string $name, $default) + public static function checked(string $name, mixed $default): string { $errors = session('errors'); diff --git a/app/Http/Controllers/Admin/ApiController.php b/app/Http/Controllers/Admin/ApiController.php index ef9c863558..a8f87bf536 100644 --- a/app/Http/Controllers/Admin/ApiController.php +++ b/app/Http/Controllers/Admin/ApiController.php @@ -11,37 +11,17 @@ use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest; class ApiController extends Controller { /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Services\Api\KeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface - */ - private $repository; - - /** - * ApplicationApiController constructor. + * ApiController constructor. */ public function __construct( - AlertsMessageBag $alert, - ApiKeyRepositoryInterface $repository, - KeyCreationService $keyCreationService + private AlertsMessageBag $alert, + private KeyCreationService $keyCreationService, ) { - $this->alert = $alert; - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; } /** @@ -50,7 +30,7 @@ public function __construct( public function index(Request $request): View { return view('admin.api.index', [ - 'keys' => $this->repository->getApplicationKeys($request->user()), + 'keys' => ApiKey::query()->where('key_type', ApiKey::TYPE_APPLICATION)->get(), ]); } @@ -96,7 +76,10 @@ public function store(StoreApplicationApiKeyRequest $request): RedirectResponse */ public function delete(Request $request, string $identifier): Response { - $this->repository->deleteApplicationKey($request->user(), $identifier); + ApiKey::query() + ->where('key_type', ApiKey::TYPE_APPLICATION) + ->where('identifier', $identifier) + ->delete(); return response('', 204); } diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 68e923ad8c..2b69330740 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -8,17 +8,11 @@ class BaseController extends Controller { - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $version; - /** * BaseController constructor. */ - public function __construct(SoftwareVersionService $version) + public function __construct(private SoftwareVersionService $version) { - $this->version = $version; } /** diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 9e6cc000a4..b37a4dade7 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Exception; -use PDOException; use Illuminate\View\View; use Pterodactyl\Models\DatabaseHost; use Illuminate\Http\RedirectResponse; @@ -19,60 +17,18 @@ class DatabaseController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $databaseRepository; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService - */ - private $updateService; - /** * DatabaseController constructor. */ public function __construct( - AlertsMessageBag $alert, - DatabaseHostRepositoryInterface $repository, - DatabaseRepositoryInterface $databaseRepository, - HostCreationService $creationService, - HostDeletionService $deletionService, - HostUpdateService $updateService, - LocationRepositoryInterface $locationRepository + private AlertsMessageBag $alert, + private DatabaseHostRepositoryInterface $repository, + private DatabaseRepositoryInterface $databaseRepository, + private HostCreationService $creationService, + private HostDeletionService $deletionService, + private HostUpdateService $updateService, + private LocationRepositoryInterface $locationRepository, ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->databaseRepository = $databaseRepository; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->locationRepository = $locationRepository; - $this->updateService = $updateService; } /** @@ -109,8 +65,8 @@ public function create(DatabaseHostFormRequest $request): RedirectResponse { try { $host = $this->creationService->handle($request->normalize()); - } catch (Exception $exception) { - if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) { + } catch (\Exception $exception) { + if ($exception instanceof \PDOException || $exception->getPrevious() instanceof \PDOException) { $this->alert->danger( sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage()) )->flash(); @@ -138,10 +94,10 @@ public function update(DatabaseHostFormRequest $request, DatabaseHost $host): Re try { $this->updateService->handle($host->id, $request->normalize()); $this->alert->success('Database host was updated successfully.')->flash(); - } catch (Exception $exception) { + } catch (\Exception $exception) { // Catch any SQL related exceptions and display them back to the user, otherwise just // throw the exception like normal and move on with it. - if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) { + if ($exception instanceof \PDOException || $exception->getPrevious() instanceof \PDOException) { $this->alert->danger( sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage()) )->flash(); diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 195977f27c..f97e55d6b9 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -1,16 +1,12 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Pterodactyl\Models\Location; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Requests\Admin\LocationFormRequest; @@ -21,54 +17,23 @@ class LocationController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Locations\LocationUpdateService - */ - protected $updateService; - /** * LocationController constructor. */ public function __construct( - AlertsMessageBag $alert, - LocationCreationService $creationService, - LocationDeletionService $deletionService, - LocationRepositoryInterface $repository, - LocationUpdateService $updateService + protected AlertsMessageBag $alert, + protected LocationCreationService $creationService, + protected LocationDeletionService $deletionService, + protected LocationRepositoryInterface $repository, + protected LocationUpdateService $updateService, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** * Return the location overview page. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { return view('admin.locations.index', [ 'locations' => $this->repository->getAllWithDetails(), @@ -78,13 +43,9 @@ public function index() /** * Return the location view page. * - * @param int $id - * - * @return \Illuminate\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($id) + public function view(int $id): View { return view('admin.locations.view', [ 'location' => $this->repository->getWithNodes($id), @@ -94,11 +55,9 @@ public function view($id) /** * Handle request to create new location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function create(LocationFormRequest $request) + public function create(LocationFormRequest $request): RedirectResponse { $location = $this->creationService->handle($request->normalize()); $this->alert->success('Location was created successfully.')->flash(); @@ -109,11 +68,9 @@ public function create(LocationFormRequest $request) /** * Handle request to update or delete location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function update(LocationFormRequest $request, Location $location) + public function update(LocationFormRequest $request, Location $location): RedirectResponse { if ($request->input('action') === 'delete') { return $this->delete($location); @@ -128,12 +85,10 @@ public function update(LocationFormRequest $request, Location $location) /** * Delete a location from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function delete(Location $location) + public function delete(Location $location): RedirectResponse { try { $this->deletionService->handle($location->id); diff --git a/app/Http/Controllers/Admin/MountController.php b/app/Http/Controllers/Admin/MountController.php index 811abfba24..a10e6b9554 100644 --- a/app/Http/Controllers/Admin/MountController.php +++ b/app/Http/Controllers/Admin/MountController.php @@ -3,11 +3,15 @@ namespace Pterodactyl\Http\Controllers\Admin; use Ramsey\Uuid\Uuid; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Nest; +use Illuminate\Http\Response; use Pterodactyl\Models\Mount; use Pterodactyl\Models\Location; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Requests\Admin\MountFormRequest; use Pterodactyl\Repositories\Eloquent\MountRepository; @@ -16,47 +20,22 @@ class MountController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $repository; - /** * MountController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestRepositoryInterface $nestRepository, - LocationRepositoryInterface $locationRepository, - MountRepository $repository + protected AlertsMessageBag $alert, + protected NestRepositoryInterface $nestRepository, + protected LocationRepositoryInterface $locationRepository, + protected MountRepository $repository, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->nestRepository = $nestRepository; - $this->locationRepository = $locationRepository; - $this->repository = $repository; } /** * Return the mount overview page. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { return view('admin.mounts.index', [ 'mounts' => $this->repository->getAllWithDetails(), @@ -66,13 +45,9 @@ public function index() /** * Return the mount view page. * - * @param string $id - * - * @return \Illuminate\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($id) + public function view(string $id): View { $nests = Nest::query()->with('eggs')->get(); $locations = Location::query()->with('nodes')->get(); @@ -87,11 +62,9 @@ public function view($id) /** * Handle request to create new mount. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function create(MountFormRequest $request) + public function create(MountFormRequest $request): RedirectResponse { $model = (new Mount())->fill($request->validated()); $model->forceFill(['uuid' => Uuid::uuid4()->toString()]); @@ -107,11 +80,9 @@ public function create(MountFormRequest $request) /** * Handle request to update or delete location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function update(MountFormRequest $request, Mount $mount) + public function update(MountFormRequest $request, Mount $mount): RedirectResponse { if ($request->input('action') === 'delete') { return $this->delete($mount); @@ -127,11 +98,9 @@ public function update(MountFormRequest $request, Mount $mount) /** * Delete a location from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception */ - public function delete(Mount $mount) + public function delete(Mount $mount): RedirectResponse { $mount->delete(); @@ -139,11 +108,9 @@ public function delete(Mount $mount) } /** - * Adds eggs to the mount's many to many relation. - * - * @return \Illuminate\Http\RedirectResponse + * Adds eggs to the mount's many-to-many relation. */ - public function addEggs(Request $request, Mount $mount) + public function addEggs(Request $request, Mount $mount): RedirectResponse { $validatedData = $request->validate([ 'eggs' => 'required|exists:eggs,id', @@ -160,11 +127,9 @@ public function addEggs(Request $request, Mount $mount) } /** - * Adds nodes to the mount's many to many relation. - * - * @return \Illuminate\Http\RedirectResponse + * Adds nodes to the mount's many-to-many relation. */ - public function addNodes(Request $request, Mount $mount) + public function addNodes(Request $request, Mount $mount): RedirectResponse { $data = $request->validate(['nodes' => 'required|exists:nodes,id']); @@ -179,11 +144,9 @@ public function addNodes(Request $request, Mount $mount) } /** - * Deletes an egg from the mount's many to many relation. - * - * @return \Illuminate\Http\Response + * Deletes an egg from the mount's many-to-many relation. */ - public function deleteEgg(Mount $mount, int $egg_id) + public function deleteEgg(Mount $mount, int $egg_id): Response { $mount->eggs()->detach($egg_id); @@ -191,11 +154,9 @@ public function deleteEgg(Mount $mount, int $egg_id) } /** - * Deletes an node from the mount's many to many relation. - * - * @return \Illuminate\Http\Response + * Deletes a node from the mount's many-to-many relation. */ - public function deleteNode(Mount $mount, int $node_id) + public function deleteNode(Mount $mount, int $node_id): Response { $mount->nodes()->detach($node_id); diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php index f8d3655153..5df25590c9 100644 --- a/app/Http/Controllers/Admin/Nests/EggController.php +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -1,19 +1,12 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; -use Javascript; use Illuminate\View\View; use Pterodactyl\Models\Egg; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Eggs\EggUpdateService; use Pterodactyl\Services\Eggs\EggCreationService; @@ -24,32 +17,18 @@ class EggController extends Controller { - protected $alert; - - protected $creationService; - - protected $deletionService; - - protected $nestRepository; - - protected $repository; - - protected $updateService; - + /** + * EggController constructor. + */ public function __construct( - AlertsMessageBag $alert, - EggCreationService $creationService, - EggDeletionService $deletionService, - EggRepositoryInterface $repository, - EggUpdateService $updateService, - NestRepositoryInterface $nestRepository + protected AlertsMessageBag $alert, + protected EggCreationService $creationService, + protected EggDeletionService $deletionService, + protected EggRepositoryInterface $repository, + protected EggUpdateService $updateService, + protected NestRepositoryInterface $nestRepository, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->nestRepository = $nestRepository; - $this->repository = $repository; - $this->updateService = $updateService; } /** @@ -60,7 +39,7 @@ public function __construct( public function create(): View { $nests = $this->nestRepository->getWithEggs(); - Javascript::put(['nests' => $nests->keyBy('id')]); + \JavaScript::put(['nests' => $nests->keyBy('id')]); return view('admin.eggs.new', ['nests' => $nests]); } @@ -73,12 +52,8 @@ public function create(): View */ public function store(EggFormRequest $request): RedirectResponse { - $data = $request->normalize(); - if (!empty($data['docker_images']) && !is_array($data['docker_images'])) { - $data['docker_images'] = array_map(function ($value) { - return trim($value); - }, explode("\n", $data['docker_images'])); - } + $data = $request->validated(); + $data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null); $egg = $this->creationService->handle($data); $this->alert->success(trans('admin/nests.eggs.notices.egg_created'))->flash(); @@ -91,7 +66,14 @@ public function store(EggFormRequest $request): RedirectResponse */ public function view(Egg $egg): View { - return view('admin.eggs.view', ['egg' => $egg]); + return view('admin.eggs.view', [ + 'egg' => $egg, + 'images' => array_map( + fn ($key, $value) => $key === $value ? $value : "$key|$value", + array_keys($egg->docker_images), + $egg->docker_images, + ), + ]); } /** @@ -103,12 +85,8 @@ public function view(Egg $egg): View */ public function update(EggFormRequest $request, Egg $egg): RedirectResponse { - $data = $request->normalize(); - if (!empty($data['docker_images']) && !is_array($data['docker_images'])) { - $data['docker_images'] = array_map(function ($value) { - return trim($value); - }, explode("\n", $data['docker_images'])); - } + $data = $request->validated(); + $data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null); $this->updateService->handle($egg, $data); $this->alert->success(trans('admin/nests.eggs.notices.updated'))->flash(); @@ -129,4 +107,22 @@ public function destroy(Egg $egg): RedirectResponse return redirect()->route('admin.nests.view', $egg->nest_id); } + + /** + * Normalizes a string of docker image data into the expected egg format. + */ + protected function normalizeDockerImages(?string $input = null): array + { + $data = array_map(fn ($value) => trim($value), explode("\n", $input ?? '')); + + $images = []; + // Iterate over the image data provided and convert it into a name => image + // pairing that is used to improve the display on the front-end. + foreach ($data as $value) { + $parts = explode('|', $value, 2); + $images[$parts[0]] = empty($parts[1]) ? $parts[0] : $parts[1]; + } + + return $images; + } } diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php index 4ae8f410b4..b01abc0add 100644 --- a/app/Http/Controllers/Admin/Nests/EggScriptController.php +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -6,6 +6,7 @@ use Pterodactyl\Models\Egg; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; @@ -13,32 +14,15 @@ class EggScriptController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Scripts\InstallScriptService - */ - protected $installScriptService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggScriptController constructor. */ public function __construct( - AlertsMessageBag $alert, - EggRepositoryInterface $repository, - InstallScriptService $installScriptService + protected AlertsMessageBag $alert, + protected EggRepositoryInterface $repository, + protected InstallScriptService $installScriptService, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->installScriptService = $installScriptService; - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Admin/Nests/EggShareController.php b/app/Http/Controllers/Admin/Nests/EggShareController.php index 32882ba5c2..85efa09a62 100644 --- a/app/Http/Controllers/Admin/Nests/EggShareController.php +++ b/app/Http/Controllers/Admin/Nests/EggShareController.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; @@ -22,38 +15,14 @@ class EggShareController extends Controller { /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggExporterService - */ - protected $exporterService; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService - */ - protected $importerService; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService - */ - protected $updateImporterService; - - /** - * OptionShareController constructor. + * EggShareController constructor. */ public function __construct( - AlertsMessageBag $alert, - EggExporterService $exporterService, - EggImporterService $importerService, - EggUpdateImporterService $updateImporterService + protected AlertsMessageBag $alert, + protected EggExporterService $exporterService, + protected EggImporterService $importerService, + protected EggUpdateImporterService $updateImporterService, ) { - $this->alert = $alert; - $this->exporterService = $exporterService; - $this->importerService = $importerService; - $this->updateImporterService = $updateImporterService; } /** @@ -61,7 +30,7 @@ public function __construct( */ public function export(Egg $egg): Response { - $filename = trim(preg_replace('/[^\w]/', '-', kebab_case($egg->name)), '-'); + $filename = trim(preg_replace('/\W/', '-', kebab_case($egg->name)), '-'); return response($this->exporterService->handle($egg->id), 200, [ 'Content-Transfer-Encoding' => 'binary', diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php index 193f3b9b53..9c24667284 100644 --- a/app/Http/Controllers/Admin/Nests/EggVariableController.php +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; @@ -14,6 +7,7 @@ use Pterodactyl\Models\EggVariable; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Services\Eggs\Variables\VariableUpdateService; @@ -23,46 +17,17 @@ class EggVariableController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Variables\VariableCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Eggs\Variables\VariableUpdateService - */ - protected $updateService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $variableRepository; - /** * EggVariableController constructor. */ public function __construct( - AlertsMessageBag $alert, - VariableCreationService $creationService, - VariableUpdateService $updateService, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $variableRepository + protected AlertsMessageBag $alert, + protected VariableCreationService $creationService, + protected VariableUpdateService $updateService, + protected EggRepositoryInterface $repository, + protected EggVariableRepositoryInterface $variableRepository, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->repository = $repository; - $this->updateService = $updateService; - $this->variableRepository = $variableRepository; } /** @@ -104,7 +69,7 @@ public function update(EggVariableFormRequest $request, Egg $egg, EggVariable $v { $this->updateService->handle($variable, $request->normalize()); $this->alert->success(trans('admin/nests.variables.notices.variable_updated', [ - 'variable' => $variable->name, + 'variable' => htmlspecialchars($variable->name), ]))->flash(); return redirect()->route('admin.nests.egg.variables', $egg->id); @@ -117,7 +82,7 @@ public function destroy(int $egg, EggVariable $variable): RedirectResponse { $this->variableRepository->delete($variable->id); $this->alert->success(trans('admin/nests.variables.notices.variable_deleted', [ - 'variable' => $variable->name, + 'variable' => htmlspecialchars($variable->name), ]))->flash(); return redirect()->route('admin.nests.egg.variables', $egg); diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php index 89e1efd777..13b40900d7 100644 --- a/app/Http/Controllers/Admin/Nests/NestController.php +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -1,17 +1,11 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Nests\NestUpdateService; use Pterodactyl\Services\Nests\NestCreationService; @@ -21,46 +15,17 @@ class NestController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Nests\NestCreationService - */ - protected $nestCreationService; - - /** - * @var \Pterodactyl\Services\Nests\NestDeletionService - */ - protected $nestDeletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Nests\NestUpdateService - */ - protected $nestUpdateService; - /** * NestController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestCreationService $nestCreationService, - NestDeletionService $nestDeletionService, - NestRepositoryInterface $repository, - NestUpdateService $nestUpdateService + protected AlertsMessageBag $alert, + protected NestCreationService $nestCreationService, + protected NestDeletionService $nestDeletionService, + protected NestRepositoryInterface $repository, + protected NestUpdateService $nestUpdateService, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->nestDeletionService = $nestDeletionService; - $this->nestCreationService = $nestCreationService; - $this->nestUpdateService = $nestUpdateService; - $this->repository = $repository; } /** @@ -91,13 +56,13 @@ public function create(): View public function store(StoreNestFormRequest $request): RedirectResponse { $nest = $this->nestCreationService->handle($request->normalize()); - $this->alert->success(trans('admin/nests.notices.created', ['name' => $nest->name]))->flash(); + $this->alert->success(trans('admin/nests.notices.created', ['name' => htmlspecialchars($nest->name)]))->flash(); return redirect()->route('admin.nests.view', $nest->id); } /** - * Return details about a nest including all of the eggs and servers per egg. + * Return details about a nest including all the eggs and servers per egg. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Http/Controllers/Admin/NodeAutoDeployController.php b/app/Http/Controllers/Admin/NodeAutoDeployController.php index a638a3f1d2..2be1a9111a 100644 --- a/app/Http/Controllers/Admin/NodeAutoDeployController.php +++ b/app/Http/Controllers/Admin/NodeAutoDeployController.php @@ -9,59 +9,30 @@ use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; class NodeAutoDeployController extends Controller { - /** - * @var \Pterodactyl\Services\Api\KeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * NodeAutoDeployController constructor. */ public function __construct( - ApiKeyRepository $repository, - Encrypter $encrypter, - KeyCreationService $keyCreationService + private Encrypter $encrypter, + private KeyCreationService $keyCreationService, ) { - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; - $this->encrypter = $encrypter; } /** - * Generates a new API key for the logged in user with only permission to read + * Generates a new API key for the logged-in user with only permission to read * nodes, and returns that as the deployment key for a node. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function __invoke(Request $request, Node $node) + public function __invoke(Request $request, Node $node): JsonResponse { - /** @var \Pterodactyl\Models\ApiKey|null $key */ - $key = $this->repository->getApplicationKeys($request->user()) - ->filter(function (ApiKey $key) { - foreach ($key->getAttributes() as $permission => $value) { - if ($permission === 'r_nodes' && $value === 1) { - return true; - } - } - - return false; - }) + $key = ApiKey::query() + ->where('user_id', $request->user()->id) + ->where('key_type', ApiKey::TYPE_APPLICATION) + ->where('r_nodes', 1) ->first(); // We couldn't find a key that exists for this user with only permission for @@ -74,7 +45,7 @@ public function __invoke(Request $request, Node $node) ], ['r_nodes' => 1]); } - return JsonResponse::create([ + return new JsonResponse([ 'node' => $node->id, 'token' => $key->identifier . $this->encrypter->decrypt($key->token), ]); diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php index d3d53fc8cb..7936772406 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -2,40 +2,18 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Spatie\QueryBuilder\QueryBuilder; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Eloquent\NodeRepository; class NodeController extends Controller { - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * NodeController constructor. - */ - public function __construct(NodeRepository $repository, Factory $view) - { - $this->view = $view; - $this->repository = $repository; - } - /** * Returns a listing of nodes on the system. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): View { $nodes = QueryBuilder::for( Node::query()->with('location')->withCount('servers') @@ -44,6 +22,6 @@ public function index(Request $request) ->allowedSorts(['id']) ->paginate(25); - return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); + return view('admin.nodes.index', ['nodes' => $nodes]); } } diff --git a/app/Http/Controllers/Admin/Nodes/NodeViewController.php b/app/Http/Controllers/Admin/Nodes/NodeViewController.php index b45b23a098..843181e896 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeViewController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -2,82 +2,41 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Repositories\Eloquent\LocationRepository; -use Pterodactyl\Repositories\Eloquent\AllocationRepository; class NodeViewController extends Controller { use JavascriptInjection; - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $versionService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\AllocationRepository - */ - private $allocationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $serverRepository; - /** * NodeViewController constructor. */ public function __construct( - AllocationRepository $allocationRepository, - LocationRepository $locationRepository, - NodeRepository $repository, - ServerRepository $serverRepository, - SoftwareVersionService $versionService, - Factory $view + private LocationRepository $locationRepository, + private NodeRepository $repository, + private ServerRepository $serverRepository, + private SoftwareVersionService $versionService, ) { - $this->repository = $repository; - $this->view = $view; - $this->versionService = $versionService; - $this->locationRepository = $locationRepository; - $this->allocationRepository = $allocationRepository; - $this->serverRepository = $serverRepository; } /** * Returns index view for a specific node on the system. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request, Node $node) + public function index(Request $request, Node $node): View { $node = $this->repository->loadLocationAndServerCount($node); - return $this->view->make('admin.nodes.view.index', [ + return view('admin.nodes.view.index', [ 'node' => $node, 'stats' => $this->repository->getUsageStats($node), 'version' => $this->versionService, @@ -86,12 +45,10 @@ public function index(Request $request, Node $node) /** * Returns the settings page for a specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function settings(Request $request, Node $node) + public function settings(Request $request, Node $node): View { - return $this->view->make('admin.nodes.view.settings', [ + return view('admin.nodes.view.settings', [ 'node' => $node, 'locations' => $this->locationRepository->all(), ]); @@ -99,26 +56,22 @@ public function settings(Request $request, Node $node) /** * Return the node configuration page for a specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function configuration(Request $request, Node $node) + public function configuration(Request $request, Node $node): View { - return $this->view->make('admin.nodes.view.configuration', compact('node')); + return view('admin.nodes.view.configuration', compact('node')); } /** * Return the node allocation management page. - * - * @return \Illuminate\Contracts\View\View */ - public function allocations(Request $request, Node $node) + public function allocations(Request $request, Node $node): View { $node = $this->repository->loadNodeAllocations($node); - $this->plainInject(['node' => Collection::wrap($node)->only(['id'])]); + $this->plainInject(['node' => Collection::make([$node])->only(['id'])]); - return $this->view->make('admin.nodes.view.allocation', [ + return view('admin.nodes.view.allocation', [ 'node' => $node, 'allocations' => Allocation::query()->where('node_id', $node->id) ->groupBy('ip') @@ -129,17 +82,15 @@ public function allocations(Request $request, Node $node) /** * Return a listing of servers that exist for this specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function servers(Request $request, Node $node) + public function servers(Request $request, Node $node): View { $this->plainInject([ - 'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token'])) + 'node' => Collection::make([$node->makeVisible(['daemon_token_id', 'daemon_token'])]) ->only(['scheme', 'fqdn', 'daemonListen', 'daemon_token_id', 'daemon_token']), ]); - return $this->view->make('admin.nodes.view.servers', [ + return view('admin.nodes.view.servers', [ 'node' => $node, 'servers' => $this->serverRepository->loadAllServersForNode($node->id, 25), ]); diff --git a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php index 476cea49c4..875b1afbea 100644 --- a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php +++ b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php @@ -11,31 +11,23 @@ class SystemInformationController extends Controller { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $repository; - /** * SystemInformationController constructor. */ - public function __construct(DaemonConfigurationRepository $repository) + public function __construct(private DaemonConfigurationRepository $repository) { - $this->repository = $repository; } /** * Returns system information from the Daemon. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function __invoke(Request $request, Node $node) + public function __invoke(Request $request, Node $node): JsonResponse { $data = $this->repository->setNode($node)->getSystemInformation(); - return JsonResponse::create([ + return new JsonResponse([ 'version' => $data['version'] ?? '', 'system' => [ 'type' => Str::title($data['os'] ?? 'Unknown'), diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 193326dee0..9933811d24 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -1,19 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Allocation; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Nodes\NodeUpdateService; use Illuminate\Cache\Repository as CacheRepository; @@ -32,103 +28,30 @@ class NodesController extends Controller { - /** - * @var \Pterodactyl\Services\Allocations\AllocationDeletionService - */ - protected $allocationDeletionService; - - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $allocationRepository; - - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - protected $assignmentService; - - /** - * @var \Illuminate\Cache\Repository - */ - protected $cache; - - /** - * @var \Pterodactyl\Services\Nodes\NodeCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $locationRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Pterodactyl\Services\Nodes\NodeUpdateService - */ - protected $updateService; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - protected $versionService; - /** * NodesController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationDeletionService $allocationDeletionService, - AllocationRepositoryInterface $allocationRepository, - AssignmentService $assignmentService, - CacheRepository $cache, - NodeCreationService $creationService, - NodeDeletionService $deletionService, - LocationRepositoryInterface $locationRepository, - NodeRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository, - NodeUpdateService $updateService, - SoftwareVersionService $versionService + protected AlertsMessageBag $alert, + protected AllocationDeletionService $allocationDeletionService, + protected AllocationRepositoryInterface $allocationRepository, + protected AssignmentService $assignmentService, + protected CacheRepository $cache, + protected NodeCreationService $creationService, + protected NodeDeletionService $deletionService, + protected LocationRepositoryInterface $locationRepository, + protected NodeRepositoryInterface $repository, + protected ServerRepositoryInterface $serverRepository, + protected NodeUpdateService $updateService, + protected SoftwareVersionService $versionService, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->allocationDeletionService = $allocationDeletionService; - $this->allocationRepository = $allocationRepository; - $this->assignmentService = $assignmentService; - $this->cache = $cache; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->locationRepository = $locationRepository; - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->updateService = $updateService; - $this->versionService = $versionService; } /** * Displays create new node page. - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View */ - public function create() + public function create(): View|RedirectResponse { $locations = $this->locationRepository->all(); if (count($locations) < 1) { @@ -143,11 +66,9 @@ public function create() /** * Post controller to create a new node on the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(NodeFormRequest $request) + public function store(NodeFormRequest $request): RedirectResponse { $node = $this->creationService->handle($request->normalize()); $this->alert->info(trans('admin/node.notices.node_created'))->flash(); @@ -158,13 +79,11 @@ public function store(NodeFormRequest $request) /** * Updates settings for a node. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateSettings(NodeFormRequest $request, Node $node) + public function updateSettings(NodeFormRequest $request, Node $node): RedirectResponse { $this->updateService->handle($node, $request->normalize(), $request->input('reset_secret') === 'on'); $this->alert->success(trans('admin/node.notices.node_updated'))->flash(); @@ -203,12 +122,8 @@ public function allocationRemoveMultiple(Request $request, int $node): Response /** * Remove all allocations for a specific IP at once on a node. - * - * @param int $node - * - * @return \Illuminate\Http\RedirectResponse */ - public function allocationRemoveBlock(Request $request, $node) + public function allocationRemoveBlock(Request $request, int $node): RedirectResponse { $this->allocationRepository->deleteWhere([ ['node_id', '=', $node], @@ -216,7 +131,7 @@ public function allocationRemoveBlock(Request $request, $node) ['ip', '=', $request->input('ip')], ]); - $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')])) + $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => htmlspecialchars($request->input('ip'))])) ->flash(); return redirect()->route('admin.nodes.view.allocation', $node); @@ -225,12 +140,10 @@ public function allocationRemoveBlock(Request $request, $node) /** * Sets an alias for a specific allocation on a node. * - * @return \Symfony\Component\HttpFoundation\Response - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function allocationSetAlias(AllocationAliasFormRequest $request) + public function allocationSetAlias(AllocationAliasFormRequest $request): \Symfony\Component\HttpFoundation\Response { $this->allocationRepository->update($request->input('allocation_id'), [ 'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'), @@ -242,16 +155,12 @@ public function allocationSetAlias(AllocationAliasFormRequest $request) /** * Creates new allocations on a node. * - * @param int|\Pterodactyl\Models\Node $node - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function createAllocation(AllocationFormRequest $request, Node $node) + public function createAllocation(AllocationFormRequest $request, Node $node): RedirectResponse { $this->assignmentService->handle($node, $request->normalize()); $this->alert->success(trans('admin/node.notices.allocations_added'))->flash(); @@ -262,13 +171,9 @@ public function createAllocation(AllocationFormRequest $request, Node $node) /** * Deletes a node from the system. * - * @param $node - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete($node) + public function delete(int|Node $node): RedirectResponse { $this->deletionService->handle($node); $this->alert->success(trans('admin/node.notices.node_deleted'))->flash(); diff --git a/app/Http/Controllers/Admin/Servers/CreateServerController.php b/app/Http/Controllers/Admin/Servers/CreateServerController.php index b8f5a320b8..54bf24926a 100644 --- a/app/Http/Controllers/Admin/Servers/CreateServerController.php +++ b/app/Http/Controllers/Admin/Servers/CreateServerController.php @@ -2,78 +2,39 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; -use JavaScript; +use Illuminate\View\View; +use Pterodactyl\Models\Nest; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Location; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Http\Requests\Admin\ServerFormRequest; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ServerCreationService; -use Pterodactyl\Repositories\Eloquent\LocationRepository; class CreateServerController extends Controller { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NestRepository - */ - private $nestRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerCreationService - */ - private $creationService; - /** * CreateServerController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestRepository $nestRepository, - LocationRepository $locationRepository, - NodeRepository $nodeRepository, - ServerRepository $repository, - ServerCreationService $creationService + private AlertsMessageBag $alert, + private NestRepository $nestRepository, + private NodeRepository $nodeRepository, + private ServerCreationService $creationService, ) { - $this->repository = $repository; - $this->nodeRepository = $nodeRepository; - $this->alert = $alert; - $this->nestRepository = $nestRepository; - $this->locationRepository = $locationRepository; - $this->creationService = $creationService; } /** * Displays the create server page. * - * @return \Illuminate\Contracts\View\Factory - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index() + public function index(): View|RedirectResponse { - $nodes = $this->nodeRepository->all(); + $nodes = Node::all(); if (count($nodes) < 1) { $this->alert->warning(trans('admin/server.alerts.node_required'))->flash(); @@ -82,9 +43,9 @@ public function index() $nests = $this->nestRepository->getWithEggs(); - Javascript::put([ + \JavaScript::put([ 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), - 'nests' => $nests->map(function ($item) { + 'nests' => $nests->map(function (Nest $item) { return array_merge($item->toArray(), [ 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); @@ -92,7 +53,7 @@ public function index() ]); return view('admin.servers.new', [ - 'locations' => $this->locationRepository->all(), + 'locations' => Location::all(), 'nests' => $nests, ]); } @@ -100,15 +61,13 @@ public function index() /** * Create a new server on the remote system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException * @throws \Throwable */ - public function store(ServerFormRequest $request) + public function store(ServerFormRequest $request): RedirectResponse { $data = $request->except(['_token']); if (!empty($data['custom_image'])) { @@ -118,10 +77,8 @@ public function store(ServerFormRequest $request) $server = $this->creationService->handle($data); - $this->alert->success( - trans('admin/server.alerts.server_created') - )->flash(); + $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - return RedirectResponse::create('/admin/servers/view/' . $server->id); + return new RedirectResponse('/admin/servers/view/' . $server->id); } } diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php index f369b7ad5e..80de68ef93 100644 --- a/app/Http/Controllers/Admin/Servers/ServerController.php +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -2,45 +2,21 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Spatie\QueryBuilder\QueryBuilder; -use Illuminate\Contracts\View\Factory; use Spatie\QueryBuilder\AllowedFilter; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Models\Filters\AdminServerFilter; -use Pterodactyl\Repositories\Eloquent\ServerRepository; class ServerController extends Controller { /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * ServerController constructor. - */ - public function __construct( - Factory $view, - ServerRepository $repository - ) { - $this->view = $view; - $this->repository = $repository; - } - - /** - * Returns all of the servers that exist on the system using a paginated result set. If + * Returns all the servers that exist on the system using a paginated result set. If * a query is passed along in the request it is also passed to the repository function. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): View { $servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation')) ->allowedFilters([ @@ -49,6 +25,6 @@ public function index(Request $request) ]) ->paginate(config()->get('pterodactyl.paginate.admin.servers')); - return $this->view->make('admin.servers.index', ['servers' => $servers]); + return view('admin.servers.index', ['servers' => $servers]); } } diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index b4afa2cabf..8c9226a658 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -2,84 +2,41 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use Carbon\CarbonImmutable; use Illuminate\Http\Request; +use Pterodactyl\Enum\JwtScope; use Pterodactyl\Models\Server; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\ServerTransfer; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Servers\TransferService; +use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\NodeRepository; -use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Repositories\Eloquent\LocationRepository; -use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; +use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class ServerTransferController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $allocationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Pterodactyl\Services\Servers\TransferService - */ - private $transferService; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $daemonConfigurationRepository; - /** * ServerTransferController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationRepositoryInterface $allocationRepository, - ServerRepository $repository, - LocationRepository $locationRepository, - NodeRepository $nodeRepository, - TransferService $transferService, - DaemonConfigurationRepository $daemonConfigurationRepository + private AlertsMessageBag $alert, + private AllocationRepositoryInterface $allocationRepository, + private ConnectionInterface $connection, + private DaemonTransferRepository $daemonTransferRepository, + private NodeJWTService $nodeJWTService, + private NodeRepository $nodeRepository, ) { - $this->alert = $alert; - $this->allocationRepository = $allocationRepository; - $this->repository = $repository; - $this->locationRepository = $locationRepository; - $this->nodeRepository = $nodeRepository; - $this->transferService = $transferService; - $this->daemonConfigurationRepository = $daemonConfigurationRepository; } /** * Starts a transfer of a server to a new node. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function transfer(Request $request, Server $server) + public function transfer(Request $request, Server $server): RedirectResponse { $validatedData = $request->validate([ 'node_id' => 'required|exists:nodes,id', @@ -93,10 +50,15 @@ public function transfer(Request $request, Server $server) // Check if the node is viable for the transfer. $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); - if ($node->isViable($server->memory, $server->disk)) { - // Check if the selected daemon is online. - $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); + if (!$node->isViable($server->memory, $server->disk)) { + $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); + return redirect()->route('admin.servers.view.manage', $server->id); + } + + $server->validateTransferState(); + + $this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) { // Create a new ServerTransfer entry. $transfer = new ServerTransfer(); @@ -105,21 +67,28 @@ public function transfer(Request $request, Server $server) $transfer->new_node = $node_id; $transfer->old_allocation = $server->allocation_id; $transfer->new_allocation = $allocation_id; - $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id'); + $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->values()->toArray(); $transfer->new_additional_allocations = $additional_allocations; $transfer->save(); - // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress. + // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress. $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); - // Request an archive from the server's current daemon. (this also checks if the daemon is online) - $this->transferService->requestArchive($server); + // Generate a token for the destination node that the source node can use to authenticate with. + $token = $this->nodeJWTService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setSubject($server->uuid) + ->setScopes(JwtScope::ServerTransfer) + ->handle($transfer->newNode, $server->uuid); - $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); - } else { - $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); - } + // Notify the source node of the pending outgoing transfer. + $this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token); + + return $transfer; + }); + + $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); return redirect()->route('admin.servers.view.manage', $server->id); } @@ -130,7 +99,7 @@ public function transfer(Request $request, Server $server) private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations) { $allocations = $additional_allocations; - array_push($allocations, $allocation_id); + $allocations[] = $allocation_id; $unassigned = $this->allocationRepository->getUnassignedAllocationIds($node_id); diff --git a/app/Http/Controllers/Admin/Servers/ServerViewController.php b/app/Http/Controllers/Admin/Servers/ServerViewController.php index 9186a8935f..0d1b7e8422 100644 --- a/app/Http/Controllers/Admin/Servers/ServerViewController.php +++ b/app/Http/Controllers/Admin/Servers/ServerViewController.php @@ -2,18 +2,16 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; -use JavaScript; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\EnvironmentService; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\MountRepository; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; @@ -22,99 +20,43 @@ class ServerViewController extends Controller { use JavascriptInjection; - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository - */ - private $databaseHostRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $mountRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NestRepository - */ - private $nestRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; - /** * ServerViewController constructor. */ public function __construct( - Factory $view, - DatabaseHostRepository $databaseHostRepository, - LocationRepository $locationRepository, - MountRepository $mountRepository, - NestRepository $nestRepository, - NodeRepository $nodeRepository, - ServerRepository $repository, - EnvironmentService $environmentService + private DatabaseHostRepository $databaseHostRepository, + private LocationRepository $locationRepository, + private MountRepository $mountRepository, + private NestRepository $nestRepository, + private NodeRepository $nodeRepository, + private EnvironmentService $environmentService, ) { - $this->view = $view; - $this->databaseHostRepository = $databaseHostRepository; - $this->locationRepository = $locationRepository; - $this->mountRepository = $mountRepository; - $this->nestRepository = $nestRepository; - $this->nodeRepository = $nodeRepository; - $this->repository = $repository; - $this->environmentService = $environmentService; } /** * Returns the index view for a server. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request, Server $server) + public function index(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.index', compact('server')); + return view('admin.servers.view.index', compact('server')); } /** * Returns the server details page. - * - * @return \Illuminate\Contracts\View\View */ - public function details(Request $request, Server $server) + public function details(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.details', compact('server')); + return view('admin.servers.view.details', compact('server')); } /** * Returns a view of server build settings. - * - * @return \Illuminate\Contracts\View\View */ - public function build(Request $request, Server $server) + public function build(Request $request, Server $server): View { $allocations = $server->node->allocations->toBase(); - return $this->view->make('admin.servers.view.build', [ + return view('admin.servers.view.build', [ 'server' => $server, 'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), 'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), @@ -124,11 +66,9 @@ public function build(Request $request, Server $server) /** * Returns the server startup management page. * - * @return \Illuminate\Contracts\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function startup(Request $request, Server $server) + public function startup(Request $request, Server $server): View { $nests = $this->nestRepository->getWithEggs(); $variables = $this->environmentService->handle($server); @@ -143,32 +83,28 @@ public function startup(Request $request, Server $server) })->keyBy('id'), ]); - return $this->view->make('admin.servers.view.startup', compact('server', 'nests')); + return view('admin.servers.view.startup', compact('server', 'nests')); } /** - * Returns all of the databases that exist for the server. - * - * @return \Illuminate\Contracts\View\View + * Returns all the databases that exist for the server. */ - public function database(Request $request, Server $server) + public function database(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.database', [ + return view('admin.servers.view.database', [ 'hosts' => $this->databaseHostRepository->all(), 'server' => $server, ]); } /** - * Returns all of the mounts that exist for the server. - * - * @return \Illuminate\Contracts\View\View + * Returns all the mounts that exist for the server. */ - public function mounts(Request $request, Server $server) + public function mounts(Request $request, Server $server): View { $server->load('mounts'); - return $this->view->make('admin.servers.view.mounts', [ + return view('admin.servers.view.mounts', [ 'mounts' => $this->mountRepository->getMountListForServer($server), 'server' => $server, ]); @@ -178,11 +114,9 @@ public function mounts(Request $request, Server $server) * Returns the base server management page, or an exception if the server * is in a state that cannot be recovered from. * - * @return \Illuminate\Contracts\View\View - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function manage(Request $request, Server $server) + public function manage(Request $request, Server $server): View { if ($server->status === Server::STATUS_INSTALL_FAILED) { throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.'); @@ -195,11 +129,11 @@ public function manage(Request $request, Server $server) $canTransfer = true; } - Javascript::put([ + \JavaScript::put([ 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), ]); - return $this->view->make('admin.servers.view.manage', [ + return view('admin.servers.view.manage', [ 'server' => $server, 'locations' => $this->locationRepository->all(), 'canTransfer' => $canTransfer, @@ -208,11 +142,9 @@ public function manage(Request $request, Server $server) /** * Returns the server deletion page. - * - * @return \Illuminate\Contracts\View\View */ - public function delete(Request $request, Server $server) + public function delete(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.delete', compact('server')); + return view('admin.servers.view.delete', compact('server')); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index a187c89228..1805a831a3 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -1,19 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Illuminate\Http\Response; use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Database; use Pterodactyl\Models\MountServer; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -40,148 +36,38 @@ class ServersController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $allocationRepository; - - /** - * @var \Pterodactyl\Services\Servers\BuildModificationService - */ - protected $buildModificationService; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $databaseRepository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - protected $databaseManagementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - protected $databasePasswordService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - protected $databaseHostRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Services\Servers\DetailsModificationService - */ - protected $detailsModificationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $mountRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - protected $reinstallService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $serverConfigurationStructureService; - - /** - * @var \Pterodactyl\Services\Servers\StartupModificationService - */ - private $startupModificationService; - - /** - * @var \Pterodactyl\Services\Servers\SuspensionService - */ - protected $suspensionService; - /** * ServersController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationRepositoryInterface $allocationRepository, - BuildModificationService $buildModificationService, - ConfigRepository $config, - DaemonServerRepository $daemonServerRepository, - DatabaseManagementService $databaseManagementService, - DatabasePasswordService $databasePasswordService, - DatabaseRepositoryInterface $databaseRepository, - DatabaseHostRepository $databaseHostRepository, - ServerDeletionService $deletionService, - DetailsModificationService $detailsModificationService, - ReinstallServerService $reinstallService, - ServerRepositoryInterface $repository, - MountRepository $mountRepository, - NestRepositoryInterface $nestRepository, - ServerConfigurationStructureService $serverConfigurationStructureService, - StartupModificationService $startupModificationService, - SuspensionService $suspensionService + protected AlertsMessageBag $alert, + protected AllocationRepositoryInterface $allocationRepository, + protected BuildModificationService $buildModificationService, + protected ConfigRepository $config, + protected DaemonServerRepository $daemonServerRepository, + protected DatabaseManagementService $databaseManagementService, + protected DatabasePasswordService $databasePasswordService, + protected DatabaseRepositoryInterface $databaseRepository, + protected DatabaseHostRepository $databaseHostRepository, + protected ServerDeletionService $deletionService, + protected DetailsModificationService $detailsModificationService, + protected ReinstallServerService $reinstallService, + protected ServerRepositoryInterface $repository, + protected MountRepository $mountRepository, + protected NestRepositoryInterface $nestRepository, + protected ServerConfigurationStructureService $serverConfigurationStructureService, + protected StartupModificationService $startupModificationService, + protected SuspensionService $suspensionService, ) { - $this->alert = $alert; - $this->allocationRepository = $allocationRepository; - $this->buildModificationService = $buildModificationService; - $this->config = $config; - $this->daemonServerRepository = $daemonServerRepository; - $this->databaseHostRepository = $databaseHostRepository; - $this->databaseManagementService = $databaseManagementService; - $this->databasePasswordService = $databasePasswordService; - $this->databaseRepository = $databaseRepository; - $this->detailsModificationService = $detailsModificationService; - $this->deletionService = $deletionService; - $this->nestRepository = $nestRepository; - $this->reinstallService = $reinstallService; - $this->repository = $repository; - $this->mountRepository = $mountRepository; - $this->serverConfigurationStructureService = $serverConfigurationStructureService; - $this->startupModificationService = $startupModificationService; - $this->suspensionService = $suspensionService; } /** * Update the details for a server. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function setDetails(Request $request, Server $server) + public function setDetails(Request $request, Server $server): RedirectResponse { $this->detailsModificationService->handle($server, $request->only([ 'owner_id', 'external_id', 'name', 'description', @@ -193,15 +79,13 @@ public function setDetails(Request $request, Server $server) } /** - * Toggles the install status for a server. - * - * @return \Illuminate\Http\RedirectResponse + * Toggles the installation status for a server. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DisplayException + * @throws DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function toggleInstall(Server $server) + public function toggleInstall(Server $server): RedirectResponse { if ($server->status === Server::STATUS_INSTALL_FAILED) { throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); @@ -219,13 +103,11 @@ public function toggleInstall(Server $server) /** * Reinstalls the server with the currently assigned service. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DisplayException + * @throws DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function reinstallServer(Server $server) + public function reinstallServer(Server $server): RedirectResponse { $this->reinstallService->handle($server); $this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash(); @@ -236,13 +118,11 @@ public function reinstallServer(Server $server) /** * Manage the suspension status for a server. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DisplayException + * @throws DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function manageSuspension(Request $request, Server $server) + public function manageSuspension(Request $request, Server $server): RedirectResponse { $this->suspensionService->toggle($server, $request->input('action')); $this->alert->success(trans('admin/server.alerts.suspension_toggled', [ @@ -255,13 +135,11 @@ public function manageSuspension(Request $request, Server $server) /** * Update the build configuration for a server. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException */ - public function updateBuild(Request $request, Server $server) + public function updateBuild(Request $request, Server $server): RedirectResponse { try { $this->buildModificationService->handle($server, $request->only([ @@ -270,7 +148,7 @@ public function updateBuild(Request $request, Server $server) 'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled', ])); } catch (DataValidationException $exception) { - throw new ValidationException($exception->validator); + throw new ValidationException($exception->getValidator()); } $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); @@ -281,12 +159,10 @@ public function updateBuild(Request $request, Server $server) /** * Start the server deletion process. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException * @throws \Throwable */ - public function delete(Request $request, Server $server) + public function delete(Request $request, Server $server): RedirectResponse { $this->deletionService->withForce($request->filled('force_delete'))->handle($server); $this->alert->success(trans('admin/server.alerts.server_deleted'))->flash(); @@ -297,11 +173,9 @@ public function delete(Request $request, Server $server) /** * Update the startup command as well as variables. * - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException */ - public function saveStartup(Request $request, Server $server) + public function saveStartup(Request $request, Server $server): RedirectResponse { $data = $request->except('_token'); if (!empty($data['custom_docker_image'])) { @@ -314,7 +188,7 @@ public function saveStartup(Request $request, Server $server) ->setUserLevel(User::USER_LEVEL_ADMIN) ->handle($server, $data); } catch (DataValidationException $exception) { - throw new ValidationException($exception->validator); + throw new ValidationException($exception->getValidator()); } $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); @@ -325,11 +199,9 @@ public function saveStartup(Request $request, Server $server) /** * Creates a new database assigned to a specific server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function newDatabase(StoreServerDatabaseRequest $request, Server $server) + public function newDatabase(StoreServerDatabaseRequest $request, Server $server): RedirectResponse { $this->databaseManagementService->create($server, [ 'database' => DatabaseManagementService::generateUniqueDatabaseName($request->input('database'), $server->id), @@ -344,18 +216,12 @@ public function newDatabase(StoreServerDatabaseRequest $request, Server $server) /** * Resets the database password for a specific database on this server. * - * @param int $server - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function resetDatabasePassword(Request $request, $server) + public function resetDatabasePassword(Request $request, Server $server): Response { - $database = $this->databaseRepository->findFirstWhere([ - ['server_id', '=', $server], - ['id', '=', $request->input('database')], - ]); + /** @var Database $database */ + $database = $server->databases()->findOrFail($request->input('database')); $this->databasePasswordService->handle($database); @@ -365,21 +231,10 @@ public function resetDatabasePassword(Request $request, $server) /** * Deletes a database from a server. * - * @param int $server - * @param int $database - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception - * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function deleteDatabase($server, $database) + public function deleteDatabase(Server $server, Database $database): Response { - $database = $this->databaseRepository->findFirstWhere([ - ['server_id', '=', $server], - ['id', '=', $database], - ]); - $this->databaseManagementService->delete($database); return response('', 204); @@ -388,14 +243,12 @@ public function deleteDatabase($server, $database) /** * Add a mount to a server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function addMount(Server $server, Mount $mount) + public function addMount(Request $request, Server $server): RedirectResponse { $mountServer = (new MountServer())->forceFill([ - 'mount_id' => $mount->id, + 'mount_id' => $request->input('mount_id'), 'server_id' => $server->id, ]); @@ -408,10 +261,8 @@ public function addMount(Server $server, Mount $mount) /** * Remove a mount from a server. - * - * @return \Illuminate\Http\RedirectResponse */ - public function deleteMount(Server $server, Mount $mount) + public function deleteMount(Server $server, Mount $mount): RedirectResponse { MountServer::where('mount_id', $mount->id)->where('server_id', $server->id)->delete(); diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index 49959bddd9..9c8bc03f9e 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -13,39 +13,15 @@ class AdvancedController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - /** * AdvancedController constructor. */ public function __construct( - AlertsMessageBag $alert, - ConfigRepository $config, - Kernel $kernel, - SettingsRepositoryInterface $settings + private AlertsMessageBag $alert, + private ConfigRepository $config, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, ) { - $this->alert = $alert; - $this->config = $config; - $this->kernel = $kernel; - $this->settings = $settings; } /** diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index 190646fd42..e89d120f43 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -16,39 +16,15 @@ class IndexController extends Controller { use AvailableLanguages; - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $versionService; - /** * IndexController constructor. */ public function __construct( - AlertsMessageBag $alert, - Kernel $kernel, - SettingsRepositoryInterface $settings, - SoftwareVersionService $versionService + private AlertsMessageBag $alert, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private SoftwareVersionService $versionService, ) { - $this->alert = $alert; - $this->kernel = $kernel; - $this->settings = $settings; - $this->versionService = $versionService; } /** diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php index 8f3e15ed6c..0725f8d494 100644 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -2,11 +2,9 @@ namespace Pterodactyl\Http\Controllers\Admin\Settings; -use Exception; use Illuminate\View\View; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; use Pterodactyl\Notifications\MailTested; use Illuminate\Support\Facades\Notification; @@ -20,46 +18,15 @@ class MailController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - /** * MailController constructor. */ public function __construct( - AlertsMessageBag $alert, - ConfigRepository $config, - Encrypter $encrypter, - Kernel $kernel, - SettingsRepositoryInterface $settings + private ConfigRepository $config, + private Encrypter $encrypter, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, ) { - $this->alert = $alert; - $this->config = $config; - $this->encrypter = $encrypter; - $this->kernel = $kernel; - $this->settings = $settings; } /** @@ -69,7 +36,7 @@ public function __construct( public function index(): View { return view('admin.settings.mail', [ - 'disabled' => $this->config->get('mail.driver') !== 'smtp', + 'disabled' => $this->config->get('mail.default') !== 'smtp', ]); } @@ -82,13 +49,13 @@ public function index(): View */ public function update(MailSettingsFormRequest $request): Response { - if ($this->config->get('mail.driver') !== 'smtp') { + if ($this->config->get('mail.default') !== 'smtp') { throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.'); } $values = $request->normalize(); - if (array_get($values, 'mail:password') === '!e') { - $values['mail:password'] = ''; + if (array_get($values, 'mail:mailers:smtp:password') === '!e') { + $values['mail:mailers:smtp:password'] = ''; } foreach ($values as $key => $value) { @@ -112,7 +79,7 @@ public function test(Request $request): Response try { Notification::route('mail', $request->user()->email) ->notify(new MailTested($request->user())); - } catch (Exception $exception) { + } catch (\Exception $exception) { return response($exception->getMessage(), 500); } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index b13ee62bfa..d44595bbec 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -2,10 +2,15 @@ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Pterodactyl\Models\Model; +use Illuminate\Support\Collection; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Spatie\QueryBuilder\QueryBuilder; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Translation\Translator; @@ -14,67 +19,31 @@ use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Http\Requests\Admin\UserFormRequest; +use Pterodactyl\Http\Requests\Admin\NewUserFormRequest; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class UserController extends Controller { use AvailableLanguages; - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Users\UserDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * @var \Pterodactyl\Services\Users\UserUpdateService - */ - protected $updateService; - /** * UserController constructor. */ public function __construct( - AlertsMessageBag $alert, - UserCreationService $creationService, - UserDeletionService $deletionService, - Translator $translator, - UserUpdateService $updateService, - UserRepositoryInterface $repository + protected AlertsMessageBag $alert, + protected UserCreationService $creationService, + protected UserDeletionService $deletionService, + protected Translator $translator, + protected UserUpdateService $updateService, + protected UserRepositoryInterface $repository, + protected ViewFactory $view, ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->translator = $translator; - $this->updateService = $updateService; } /** * Display user index page. - * - * @return \Illuminate\View\View */ - public function index(Request $request) + public function index(Request $request): View { $users = QueryBuilder::for( User::query()->select('users.*') @@ -85,6 +54,7 @@ public function index(Request $request) ->groupBy('users.id') ) ->allowedFilters(['username', 'email', 'uuid']) + ->defaultSort('-root_admin') ->allowedSorts(['id', 'uuid']) ->paginate(50); @@ -93,10 +63,8 @@ public function index(Request $request) /** * Display new user page. - * - * @return \Illuminate\View\View */ - public function create() + public function create(): View { return view('admin.users.new', [ 'languages' => $this->getAvailableLanguages(true), @@ -105,10 +73,8 @@ public function create() /** * Display user view page. - * - * @return \Illuminate\View\View */ - public function view(User $user) + public function view(User $user): View { return view('admin.users.view', [ 'user' => $user, @@ -119,15 +85,13 @@ public function view(User $user) /** * Delete a user from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function delete(Request $request, User $user) + public function delete(Request $request, User $user): RedirectResponse { - if ($request->user()->id === $user->id) { - throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers')); + if ($request->user()->is($user)) { + throw new DisplayException(__('admin/user.exceptions.delete_self')); } $this->deletionService->handle($user); @@ -138,12 +102,10 @@ public function delete(Request $request, User $user) /** * Create a user. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception * @throws \Throwable */ - public function store(UserFormRequest $request) + public function store(NewUserFormRequest $request): RedirectResponse { $user = $this->creationService->handle($request->normalize()); $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); @@ -154,12 +116,10 @@ public function store(UserFormRequest $request) /** * Update a user on the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UserFormRequest $request, User $user) + public function update(UserFormRequest $request, User $user): RedirectResponse { $this->updateService ->setUserLevel(User::USER_LEVEL_ADMIN) @@ -172,22 +132,22 @@ public function update(UserFormRequest $request, User $user) /** * Get a JSON response of users on the system. - * - * @return \Illuminate\Support\Collection|\Pterodactyl\Models\Model */ - public function json(Request $request) + public function json(Request $request): Model|Collection { $users = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25); // Handle single user requests. if ($request->query('user_id')) { $user = User::query()->findOrFail($request->input('user_id')); + // @phpstan-ignore-next-line property.notFound $user->md5 = md5(strtolower($user->email)); return $user; } return $users->map(function ($item) { + // @phpstan-ignore-next-line property.notFound $item->md5 = md5(strtolower($item->email)); return $item; diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index 6078b6f2c5..4ffce692c8 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -13,15 +13,9 @@ abstract class ApplicationApiController extends Controller { - /** - * @var \Illuminate\Http\Request - */ - protected $request; + protected Request $request; - /** - * @var \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal - */ - protected $fractal; + protected Fractal $fractal; /** * ApplicationApiController constructor. @@ -30,7 +24,7 @@ public function __construct() { Container::getInstance()->call([$this, 'loadDependencies']); - // Parse all of the includes to use on this request. + // Parse all the includes to use on this request. $input = $this->request->input('include', []); $input = is_array($input) ? $input : explode(',', $input); @@ -55,21 +49,23 @@ public function loadDependencies(Fractal $fractal, Request $request) /** * Return an instance of an application transformer. * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer + * + * @param class-string $abstract + * + * @return T + * + * @noinspection PhpDocSignatureInspection */ public function getTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - $transformer->setKey($this->request->attributes->get('api_key')); - - Assert::isInstanceOf($transformer, BaseTransformer::class); + Assert::subclassOf($abstract, BaseTransformer::class); // @phpstan-ignore staticMethod.alreadyNarrowedType - return $transformer; + return $abstract::fromRequest($this->request); } /** - * Return a HTTP/204 response for the API. + * Return an HTTP/204 response for the API. */ protected function returnNoContent(): Response { diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index b7e82396a8..ae67d13907 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -9,7 +9,6 @@ use Pterodactyl\Services\Locations\LocationUpdateService; use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; -use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest; @@ -20,45 +19,19 @@ class LocationController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Locations\LocationUpdateService - */ - private $updateService; - /** * LocationController constructor. */ public function __construct( - LocationCreationService $creationService, - LocationDeletionService $deletionService, - LocationRepositoryInterface $repository, - LocationUpdateService $updateService + private LocationCreationService $creationService, + private LocationDeletionService $deletionService, + private LocationUpdateService $updateService, ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** - * Return all of the locations currently registered on the Panel. + * Return all the locations currently registered on the Panel. */ public function index(GetLocationsRequest $request): array { @@ -75,15 +48,15 @@ public function index(GetLocationsRequest $request): array /** * Return a single location. */ - public function view(GetLocationRequest $request): array + public function view(GetLocationRequest $request, Location $location): array { - return $this->fractal->item($request->getModel(Location::class)) + return $this->fractal->item($location) ->transformWith($this->getTransformer(LocationTransformer::class)) ->toArray(); } /** - * Store a new location on the Panel and return a HTTP/201 response code with the + * Store a new location on the Panel and return an HTTP/201 response code with the * new location attached. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -108,9 +81,9 @@ public function store(StoreLocationRequest $request): JsonResponse * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateLocationRequest $request): array + public function update(UpdateLocationRequest $request, Location $location): array { - $location = $this->updateService->handle($request->getModel(Location::class), $request->validated()); + $location = $this->updateService->handle($location, $request->validated()); return $this->fractal->item($location) ->transformWith($this->getTransformer(LocationTransformer::class)) @@ -122,9 +95,9 @@ public function update(UpdateLocationRequest $request): array * * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException */ - public function delete(DeleteLocationRequest $request): Response + public function delete(DeleteLocationRequest $request, Location $location): Response { - $this->deletionService->handle($request->getModel(Location::class)); + $this->deletionService->handle($location); return response('', 204); } diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php index 1eb596f990..83c3f77a0e 100644 --- a/app/Http/Controllers/Api/Application/Nests/EggController.php +++ b/app/Http/Controllers/Api/Application/Nests/EggController.php @@ -4,7 +4,6 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest; @@ -12,31 +11,12 @@ class EggController extends ApplicationApiController { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * EggController constructor. - */ - public function __construct(EggRepositoryInterface $repository) - { - parent::__construct(); - - $this->repository = $repository; - } - /** * Return all eggs that exist for a given nest. */ - public function index(GetEggsRequest $request): array + public function index(GetEggsRequest $request, Nest $nest): array { - $eggs = $this->repository->findWhere([ - ['nest_id', '=', $request->getModel(Nest::class)->id], - ]); - - return $this->fractal->collection($eggs) + return $this->fractal->collection($nest->eggs) ->transformWith($this->getTransformer(EggTransformer::class)) ->toArray(); } @@ -44,9 +24,9 @@ public function index(GetEggsRequest $request): array /** * Return a single egg that exists on the specified nest. */ - public function view(GetEggRequest $request): array + public function view(GetEggRequest $request, Nest $nest, Egg $egg): array { - return $this->fractal->item($request->getModel(Egg::class)) + return $this->fractal->item($egg) ->transformWith($this->getTransformer(EggTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index b66872d231..f0044f53c9 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -10,19 +10,12 @@ class NestController extends ApplicationApiController { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; - /** * NestController constructor. */ - public function __construct(NestRepositoryInterface $repository) + public function __construct(private NestRepositoryInterface $repository) { parent::__construct(); - - $this->repository = $repository; } /** @@ -40,9 +33,9 @@ public function index(GetNestsRequest $request): array /** * Return information about a single Nest model. */ - public function view(GetNestsRequest $request): array + public function view(GetNestsRequest $request, Nest $nest): array { - return $this->fractal->item($request->getModel(Nest::class)) + return $this->fractal->item($nest) ->transformWith($this->getTransformer(NestTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 5a66e81756..07c2017234 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -5,6 +5,9 @@ use Pterodactyl\Models\Node; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; +use Spatie\QueryBuilder\QueryBuilder; +use Spatie\QueryBuilder\AllowedFilter; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AllocationDeletionService; use Pterodactyl\Transformers\Api\Application\AllocationTransformer; @@ -15,35 +18,35 @@ class AllocationController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - private $assignmentService; - - /** - * @var \Pterodactyl\Services\Allocations\AllocationDeletionService - */ - private $deletionService; - /** * AllocationController constructor. */ public function __construct( - AssignmentService $assignmentService, - AllocationDeletionService $deletionService + private AssignmentService $assignmentService, + private AllocationDeletionService $deletionService, ) { parent::__construct(); - - $this->assignmentService = $assignmentService; - $this->deletionService = $deletionService; } /** - * Return all of the allocations that exist for a given node. + * Return all the allocations that exist for a given node. */ public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = $node->allocations()->paginate($request->query('per_page') ?? 50); + $allocations = QueryBuilder::for($node->allocations()) + ->allowedFilters([ + AllowedFilter::exact('ip'), + AllowedFilter::exact('port'), + 'ip_alias', + AllowedFilter::callback('server_id', function (Builder $builder, $value) { + if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) { + return $builder->whereNull('server_id'); + } + + return $builder->where('server_id', $value); + }), + ]) + ->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($allocations) ->transformWith($this->getTransformer(AllocationTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php index 058f542b6e..2d812a89bb 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -13,11 +13,9 @@ class NodeConfigurationController extends ApplicationApiController * Returns the configuration information for a node. This allows for automated deployments * to remote machines so long as an API key is provided to the machine to make the request * with, and the node is known. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(GetNodeRequest $request, Node $node) + public function __invoke(GetNodeRequest $request, Node $node): JsonResponse { - return JsonResponse::create($node->getConfiguration()); + return new JsonResponse($node->getConfiguration()); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index 2daf187c2f..5c40f77f66 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -8,7 +8,6 @@ use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Nodes\NodeDeletionService; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Transformers\Api\Application\NodeTransformer; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest; @@ -19,45 +18,19 @@ class NodeController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Nodes\NodeUpdateService - */ - private $updateService; - /** * NodeController constructor. */ public function __construct( - NodeCreationService $creationService, - NodeDeletionService $deletionService, - NodeUpdateService $updateService, - NodeRepositoryInterface $repository + private NodeCreationService $creationService, + private NodeDeletionService $deletionService, + private NodeUpdateService $updateService, ) { parent::__construct(); - - $this->repository = $repository; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->updateService = $updateService; } /** - * Return all of the nodes currently available on the Panel. + * Return all the nodes currently available on the Panel. */ public function index(GetNodesRequest $request): array { @@ -82,7 +55,7 @@ public function view(GetNodeRequest $request, Node $node): array } /** - * Create a new node on the Panel. Returns the created node and a HTTP/201 + * Create a new node on the Panel. Returns the created node and an HTTP/201 * status response on success. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index 9d5c75a058..4b49fa9c6a 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -9,19 +9,12 @@ class NodeDeploymentController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private $viableNodesService; - /** * NodeDeploymentController constructor. */ - public function __construct(FindViableNodesService $viableNodesService) + public function __construct(private FindViableNodesService $viableNodesService) { parent::__construct(); - - $this->viableNodesService = $viableNodesService; } /** diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 73f96737de..43fc9a551c 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -8,7 +8,6 @@ use Illuminate\Http\JsonResponse; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Services\Databases\DatabaseManagementService; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Transformers\Api\Application\ServerDatabaseTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest; @@ -18,34 +17,14 @@ class DatabaseController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $databaseManagementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - private $databasePasswordService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $repository; - /** * DatabaseController constructor. */ public function __construct( - DatabaseManagementService $databaseManagementService, - DatabasePasswordService $databasePasswordService, - DatabaseRepositoryInterface $repository + private DatabaseManagementService $databaseManagementService, + private DatabasePasswordService $databasePasswordService, ) { parent::__construct(); - - $this->databaseManagementService = $databaseManagementService; - $this->databasePasswordService = $databasePasswordService; - $this->repository = $repository; } /** @@ -78,7 +57,7 @@ public function resetPassword(ServerDatabaseWriteRequest $request, Server $serve { $this->databasePasswordService->handle($database); - return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } /** @@ -105,12 +84,10 @@ public function store(StoreServerDatabaseRequest $request, Server $server): Json /** * Handle a request to delete a specific server database from the Panel. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(ServerDatabaseWriteRequest $request): Response + public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response { - $this->databaseManagementService->delete($request->getModel(Database::class)); + $this->databaseManagementService->delete($database); return response('', 204); } diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 0cecb977a9..869472f724 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; +use Pterodactyl\Models\Server; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Servers\GetExternalServerRequest; @@ -11,9 +12,11 @@ class ExternalServerController extends ApplicationApiController /** * Retrieve a specific server from the database using its external ID. */ - public function index(GetExternalServerRequest $request): array + public function index(GetExternalServerRequest $request, string $external_id): array { - return $this->fractal->item($request->getServerModel()) + $server = Server::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index fb1871f690..b1e80c4719 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -8,7 +8,6 @@ use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; @@ -18,43 +17,23 @@ class ServerController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\ServerCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * ServerController constructor. */ public function __construct( - ServerCreationService $creationService, - ServerDeletionService $deletionService, - ServerRepositoryInterface $repository + private ServerCreationService $creationService, + private ServerDeletionService $deletionService, ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; } /** - * Return all of the servers that currently exist on the Panel. + * Return all the servers that currently exist on the Panel. */ public function index(GetServersRequest $request): array { $servers = QueryBuilder::for(Server::query()) - ->allowedFilters(['uuid', 'uuidShort', 'name', 'image', 'external_id']) + ->allowedFilters(['uuid', 'uuidShort', 'name', 'description', 'image', 'external_id']) ->allowedSorts(['id', 'uuid']) ->paginate($request->query('per_page') ?? 50); @@ -86,14 +65,16 @@ public function store(StoreServerRequest $request): JsonResponse /** * Show a single server transformed for the application API. */ - public function view(GetServerRequest $request): array + public function view(GetServerRequest $request, Server $server): array { - return $this->fractal->item($request->getModel(Server::class)) + return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } /** + * Deletes a server. + * * @throws \Pterodactyl\Exceptions\DisplayException */ public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index 06bbf6bca5..20ea05422e 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -12,27 +12,14 @@ class ServerDetailsController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\BuildModificationService - */ - private $buildModificationService; - - /** - * @var \Pterodactyl\Services\Servers\DetailsModificationService - */ - private $detailsModificationService; - /** * ServerDetailsController constructor. */ public function __construct( - BuildModificationService $buildModificationService, - DetailsModificationService $detailsModificationService + private BuildModificationService $buildModificationService, + private DetailsModificationService $detailsModificationService, ) { parent::__construct(); - - $this->buildModificationService = $buildModificationService; - $this->detailsModificationService = $detailsModificationService; } /** @@ -42,14 +29,14 @@ public function __construct( * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function details(UpdateServerDetailsRequest $request): array + public function details(UpdateServerDetailsRequest $request, Server $server): array { - $server = $this->detailsModificationService->returnUpdatedModel()->handle( - $request->getModel(Server::class), + $updated = $this->detailsModificationService->returnUpdatedModel()->handle( + $server, $request->validated() ); - return $this->fractal->item($server) + return $this->fractal->item($updated) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index ddbbbe1ddb..51a1476fec 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -12,26 +12,13 @@ class ServerManagementController extends ApplicationApiController { /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - private $reinstallServerService; - - /** - * @var \Pterodactyl\Services\Servers\SuspensionService - */ - private $suspensionService; - - /** - * SuspensionController constructor. + * ServerManagementController constructor. */ public function __construct( - ReinstallServerService $reinstallServerService, - SuspensionService $suspensionService + private ReinstallServerService $reinstallServerService, + private SuspensionService $suspensionService, ) { parent::__construct(); - - $this->reinstallServerService = $reinstallServerService; - $this->suspensionService = $suspensionService; } /** @@ -41,7 +28,7 @@ public function __construct( */ public function suspend(ServerWriteRequest $request, Server $server): Response { - $this->suspensionService->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->suspensionService->toggle($server); return $this->returnNoContent(); } diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index e8300754fe..6c2da078a4 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -11,19 +11,12 @@ class StartupController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\StartupModificationService - */ - private $modificationService; - /** * StartupController constructor. */ - public function __construct(StartupModificationService $modificationService) + public function __construct(private StartupModificationService $modificationService) { parent::__construct(); - - $this->modificationService = $modificationService; } /** @@ -34,11 +27,11 @@ public function __construct(StartupModificationService $modificationService) * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(UpdateServerStartupRequest $request): array + public function index(UpdateServerStartupRequest $request, Server $server): array { $server = $this->modificationService ->setUserLevel(User::USER_LEVEL_ADMIN) - ->handle($request->getModel(Server::class), $request->validated()); + ->handle($server, $request->validated()); return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index a253b6e195..2a8f4f07e7 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; +use Pterodactyl\Models\User; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Users\GetExternalUserRequest; @@ -11,9 +12,11 @@ class ExternalUserController extends ApplicationApiController /** * Retrieve a specific user from the database using their external ID. */ - public function index(GetExternalUserRequest $request): array + public function index(GetExternalUserRequest $request, string $external_id): array { - return $this->fractal->item($request->getUserModel()) + $user = User::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 0a5675e2d9..4e251573df 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -3,13 +3,11 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; use Pterodactyl\Models\User; -use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest; use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest; @@ -19,41 +17,15 @@ class UserController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Users\UserDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Users\UserUpdateService - */ - private $updateService; - /** * UserController constructor. */ public function __construct( - UserRepositoryInterface $repository, - UserCreationService $creationService, - UserDeletionService $deletionService, - UserUpdateService $updateService + private UserCreationService $creationService, + private UserDeletionService $deletionService, + private UserUpdateService $updateService, ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** @@ -107,7 +79,7 @@ public function update(UpdateUserRequest $request, User $user): array } /** - * Store a new user on the system. Returns the created user and a HTTP/201 + * Store a new user on the system. Returns the created user and an HTTP/201 * header on successful creation. * * @throws \Exception diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index ead296d3ca..8f733cd227 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -6,32 +6,27 @@ use Illuminate\Http\Response; use Illuminate\Auth\AuthManager; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; +use Illuminate\Support\Facades\RateLimiter; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Transformers\Api\Client\AccountTransformer; use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest; use Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; class AccountController extends ClientApiController { /** - * @var \Pterodactyl\Services\Users\UserUpdateService + * The number of seconds that must elapse before the email change throttle resets. */ - private $updateService; - - /** - * @var \Illuminate\Auth\SessionGuard - */ - private $sessionGuard; + private const EMAIL_UPDATE_THROTTLE = 60 * 60 * 24; /** * AccountController constructor. */ - public function __construct(AuthManager $sessionGuard, UserUpdateService $updateService) + public function __construct(private AuthManager $manager, private UserUpdateService $updateService) { parent::__construct(); - - $this->updateService = $updateService; - $this->sessionGuard = $sessionGuard; } public function index(Request $request): array @@ -43,13 +38,27 @@ public function index(Request $request): array /** * Update the authenticated user's email address. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function updateEmail(UpdateEmailRequest $request): JsonResponse { - $this->updateService->handle($request->user(), $request->validated()); + $user = $request->user(); + // Only allow a user to change their email three times in the span + // of 24 hours. This prevents malicious users from trying to find + // existing accounts in the system by constantly changing their email. + if (RateLimiter::tooManyAttempts($key = "user:update-email:{$user->uuid}", 3)) { + throw new TooManyRequestsHttpException(message: 'Your email address has been changed too many times today. Please try again later.'); + } + + $original = $user->email; + if (mb_strtolower($original) !== mb_strtolower($request->validated('email'))) { + RateLimiter::hit($key, self::EMAIL_UPDATE_THROTTLE); + + $this->updateService->handle($user, $request->validated()); + + Activity::event('user:account.email-changed') + ->property(['old' => $original, 'new' => $request->validated('email')]) + ->log(); + } return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -62,15 +71,21 @@ public function updateEmail(UpdateEmailRequest $request): JsonResponse */ public function updatePassword(UpdatePasswordRequest $request): JsonResponse { - $user = $this->updateService->handle($request->user(), $request->validated()); + $user = Activity::event('user:account.password-changed')->transaction(function () use ($request) { + return $this->updateService->handle($request->user(), $request->validated()); + }); + $guard = $this->manager->guard(); // If you do not update the user in the session you'll end up working with a // cached copy of the user that does not include the updated password. Do this // to correctly store the new user details in the guard and allow the logout // other devices functionality to work. - $this->sessionGuard->setUser($user); + $guard->setUser($user); - $this->sessionGuard->logoutOtherDevices($request->input('password')); + // This method doesn't exist in the stateless Sanctum world. + if (method_exists($guard, 'logoutOtherDevices')) { // @phpstan-ignore function.alreadyNarrowedType + $guard->logoutOtherDevices($request->input('password')); + } return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/ActivityLogController.php b/app/Http/Controllers/Api/Client/ActivityLogController.php new file mode 100644 index 0000000000..dbbd06c06b --- /dev/null +++ b/app/Http/Controllers/Api/Client/ActivityLogController.php @@ -0,0 +1,30 @@ +user()->activity()) + ->with('actor') + ->allowedFilters([AllowedFilter::partial('event')]) + ->allowedSorts(['timestamp']) + ->whereNotIn('activity_logs.event', ActivityLog::DISABLED_EVENTS) + ->paginate(min($request->query('per_page', 25), 100)) + ->appends($request->query()); + + return $this->fractal->collection($activity) + ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 34694d66c4..f217220bba 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -4,53 +4,19 @@ use Pterodactyl\Models\ApiKey; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; +use Illuminate\Support\Facades\DB; use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest; class ApiKeyController extends ClientApiController { /** - * @var \Pterodactyl\Services\Api\KeyCreationService + * Returns all the API keys that exist for the given client. */ - private $keyCreationService; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository - */ - private $repository; - - /** - * ApiKeyController constructor. - */ - public function __construct( - Encrypter $encrypter, - KeyCreationService $keyCreationService, - ApiKeyRepository $repository - ) { - parent::__construct(); - - $this->encrypter = $encrypter; - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; - } - - /** - * Returns all of the API keys that exist for the given client. - * - * @return array - */ - public function index(ClientApiRequest $request) + public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->apiKeys) ->transformWith($this->getTransformer(ApiKeyTransformer::class)) @@ -60,48 +26,49 @@ public function index(ClientApiRequest $request) /** * Store a new API key for a user's account. * - * @return array - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DisplayException */ - public function store(StoreApiKeyRequest $request) + public function store(StoreApiKeyRequest $request): array { - if ($request->user()->apiKeys->count() >= 5) { - throw new DisplayException('You have reached the account limit for number of API keys.'); - } + $token = DB::transaction(function () use ($request) { + if ($request->user()->apiKeys()->lockForUpdate()->count() >= 25) { + throw new DisplayException('You have reached the account limit for number of API keys.'); + } + + return $request->user()->createToken( + $request->input('description'), + $request->input('allowed_ips') + ); + }); - $key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ - 'user_id' => $request->user()->id, - 'memo' => $request->input('description'), - 'allowed_ips' => $request->input('allowed_ips') ?? [], - ]); + Activity::event('user:api-key.create') + ->subject($token->accessToken) + ->property('identifier', $token->accessToken->identifier) + ->log(); - return $this->fractal->item($key) + return $this->fractal->item($token->accessToken) ->transformWith($this->getTransformer(ApiKeyTransformer::class)) - ->addMeta([ - 'secret_token' => $this->encrypter->decrypt($key->token), - ]) + ->addMeta(['secret_token' => $token->plainTextToken]) ->toArray(); } /** * Deletes a given API key. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(ClientApiRequest $request, string $identifier) + public function delete(ClientApiRequest $request, string $identifier): JsonResponse { - $response = $this->repository->deleteWhere([ - 'key_type' => ApiKey::TYPE_ACCOUNT, - 'user_id' => $request->user()->id, - 'identifier' => $identifier, - ]); + /** @var ApiKey $key */ + $key = $request->user()->apiKeys() + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->where('identifier', $identifier) + ->firstOrFail(); + + Activity::event('user:api-key.delete') + ->property('identifier', $key->identifier) + ->log(); - if (!$response) { - throw new NotFoundHttpException(); - } + $key->delete(); - return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index c7f6f1a49c..6dcf0492c4 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -3,8 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; -use Illuminate\Container\Container; -use Pterodactyl\Transformers\Daemon\BaseDaemonTransformer; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; @@ -12,10 +10,8 @@ abstract class ClientApiController extends ApplicationApiController { /** * Returns only the includes which are valid for the given transformer. - * - * @return string[] */ - protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []) + protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array { $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { return in_array($datum, $transformer->getAvailableIncludes()); @@ -26,10 +22,8 @@ protected function getIncludesForTransformer(BaseClientTransformer $transformer, /** * Returns the parsed includes for this request. - * - * @return string[] */ - protected function parseIncludes() + protected function parseIncludes(): array { $includes = $this->request->query('include') ?? []; @@ -45,22 +39,18 @@ protected function parseIncludes() /** * Return an instance of an application transformer. * - * @return \Pterodactyl\Transformers\Api\Client\BaseClientTransformer + * @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer + * + * @param class-string $abstract + * + * @return T + * + * @noinspection PhpDocSignatureInspection */ public function getTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - Assert::isInstanceOfAny($transformer, [ - BaseClientTransformer::class, - BaseDaemonTransformer::class, - ]); - - if ($transformer instanceof BaseClientTransformer) { - $transformer->setKey($this->request->attributes->get('api_key')); - $transformer->setUser($this->request->user()); - } + Assert::subclassOf($abstract, BaseClientTransformer::class); // @phpstan-ignore staticMethod.alreadyNarrowedType - return $transformer; + return $abstract::fromRequest($this->request); } } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 16923c56fb..9afb72628f 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -7,29 +7,21 @@ use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Pterodactyl\Models\Filters\MultiFieldServerFilter; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; use Pterodactyl\Http\Requests\Api\Client\GetServersRequest; class ClientController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - /** * ClientController constructor. */ - public function __construct(ServerRepository $repository) + public function __construct() { parent::__construct(); - - $this->repository = $repository; } /** - * Return all of the servers available to the client making the API + * Return all the servers available to the client making the API * request, including servers the user has access to as a subuser. */ public function index(GetServersRequest $request): array @@ -43,13 +35,14 @@ public function index(GetServersRequest $request): array )->allowedFilters([ 'uuid', 'name', + 'description', 'external_id', AllowedFilter::custom('*', new MultiFieldServerFilter()), ]); $type = $request->input('type'); - // Either return all of the servers the user has access to because they are an admin `?type=admin` or - // just return all of the servers the user has access to because they are the owner or a subuser of the + // Either return all the servers the user has access to because they are an admin `?type=admin` or + // just return all the servers the user has access to because they are the owner or a subuser of the // server. If ?type=admin-all is passed all servers on the system will be returned to the user, rather // than only servers they can see because they are an admin. if (in_array($type, ['admin', 'admin-all'])) { @@ -74,11 +67,9 @@ public function index(GetServersRequest $request): array } /** - * Returns all of the subuser permissions available on the system. - * - * @return array + * Returns all the subuser permissions available on the system. */ - public function permissions() + public function permissions(): array { return [ 'object' => 'system_permissions', diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php new file mode 100644 index 0000000000..aa0f0c6860 --- /dev/null +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -0,0 +1,67 @@ +fractal->collection($request->user()->sshKeys) + ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->toArray(); + } + + /** + * Stores a new SSH key for the authenticated user's account. + */ + public function store(StoreSSHKeyRequest $request): array + { + $model = $request->user()->sshKeys()->create([ + 'name' => $request->input('name'), + 'public_key' => $request->getPublicKey(), + 'fingerprint' => $request->getKeyFingerprint(), + ]); + + Activity::event('user:ssh-key.create') + ->subject($model) + ->property('fingerprint', $request->getKeyFingerprint()) + ->log(); + + return $this->fractal->item($model) + ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->toArray(); + } + + /** + * Deletes an SSH key from the user's account. + */ + public function delete(ClientApiRequest $request): JsonResponse + { + $this->validate($request, ['fingerprint' => ['required', 'string']]); + + $key = $request->user()->sshKeys() + ->where('fingerprint', $request->input('fingerprint')) + ->first(); + + if (!is_null($key)) { + $key->delete(); + + Activity::event('user:ssh-key.delete') + ->subject($key) + ->property('fingerprint', $key->fingerprint) + ->log(); + } + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php new file mode 100644 index 0000000000..f569c41220 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php @@ -0,0 +1,54 @@ +authorize(Permission::ACTION_ACTIVITY_READ, $server); + + $activity = QueryBuilder::for($server->activity()) + ->with('actor') + ->allowedSorts(['timestamp']) + ->allowedFilters([AllowedFilter::partial('event')]) + ->whereNotIn('activity_logs.event', ActivityLog::DISABLED_EVENTS) + ->when(config('activity.hide_admin_activity'), function (Builder $builder) use ($server) { + // We could do this with a query and a lot of joins, but that gets pretty + // painful so for now we'll execute a simpler query. + $subusers = $server->subusers()->pluck('user_id')->merge($server->owner_id); + + $builder->select('activity_logs.*') + ->leftJoin('users', function (JoinClause $join) { + $join->on('users.id', 'activity_logs.actor_id') + ->where('activity_logs.actor_type', (new User())->getMorphClass()); + }) + ->where(function (Builder $builder) use ($subusers) { + $builder->whereNull('users.id') + ->orWhere('users.root_admin', 0) + ->orWhereIn('users.id', $subusers); + }); + }) + ->paginate(min($request->query('per_page', 25), 100)) + ->appends($request->query()); + + return $this->fractal->collection($activity) + ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index 67b54cd287..723f3c9167 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -5,8 +5,8 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Illuminate\Auth\Access\AuthorizationException; use Pterodactyl\Services\Backups\DeleteBackupService; @@ -18,39 +18,28 @@ use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest; class BackupController extends ClientApiController { - private InitiateBackupService $initiateBackupService; - private DeleteBackupService $deleteBackupService; - private DownloadLinkService $downloadLinkService; - private DaemonBackupRepository $daemonRepository; - private BackupRepository $repository; - /** * BackupController constructor. */ public function __construct( - DaemonBackupRepository $daemonRepository, - DeleteBackupService $deleteBackupService, - InitiateBackupService $initiateBackupService, - DownloadLinkService $downloadLinkService, - BackupRepository $repository + private DaemonBackupRepository $daemonRepository, + private DeleteBackupService $deleteBackupService, + private InitiateBackupService $initiateBackupService, + private DownloadLinkService $downloadLinkService, + private BackupRepository $repository, ) { parent::__construct(); - - $this->repository = $repository; - $this->initiateBackupService = $initiateBackupService; - $this->deleteBackupService = $deleteBackupService; - $this->downloadLinkService = $downloadLinkService; - $this->daemonRepository = $daemonRepository; } /** * Returns all the backups for a given server instance in a paginated * result set. * - * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws AuthorizationException */ public function index(Request $request, Server $server): array { @@ -77,22 +66,26 @@ public function index(Request $request, Server $server): array */ public function store(StoreBackupRequest $request, Server $server): array { - /** @var \Pterodactyl\Models\Backup $backup */ - $backup = $server->audit(AuditLog::SERVER__BACKUP_STARTED, function (AuditLog $model, Server $server) use ($request) { - $action = $this->initiateBackupService - ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); - - // Only set the lock status if the user even has permission to delete backups, - // otherwise ignore this status. This gets a little funky since it isn't clear - // how best to allow a user to create a backup that is locked without also preventing - // them from just filling up a server with backups that can never be deleted? - if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { - $action->setIsLocked((bool) $request->input('is_locked')); - } + $action = $this->initiateBackupService + ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); + + // Only set the lock status if the user even has permission to delete backups, + // otherwise ignore this status. This gets a little funky since it isn't clear + // how best to allow a user to create a backup that is locked without also preventing + // them from just filling up a server with backups that can never be deleted? + if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + $action->setIsLocked($request->boolean('is_locked')); + } + + $backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) { + $server->backups()->lockForUpdate()->count(); $backup = $action->handle($server, $request->input('name')); - $model->metadata = ['backup_uuid' => $backup->uuid]; + $log->subject($backup)->property([ + 'name' => $backup->name, + 'locked' => $request->boolean('is_locked'), + ]); return $backup; }); @@ -106,7 +99,7 @@ public function store(StoreBackupRequest $request, Server $server): array * Toggles the lock status of a given backup for a server. * * @throws \Throwable - * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws AuthorizationException */ public function toggleLock(Request $request, Server $server, Backup $backup): array { @@ -114,14 +107,11 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar throw new AuthorizationException(); } - $action = $backup->is_locked ? AuditLog::SERVER__BACKUP_UNLOCKED : AuditLog::SERVER__BACKUP_LOCKED; - $server->audit($action, function (AuditLog $audit) use ($backup) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; + $action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock'; - $backup->update(['is_locked' => !$backup->is_locked]); - }); + $backup->update(['is_locked' => !$backup->is_locked]); - $backup->refresh(); + Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); return $this->fractal->item($backup) ->transformWith($this->getTransformer(BackupTransformer::class)) @@ -131,7 +121,7 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar /** * Returns information about a single backup. * - * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws AuthorizationException */ public function view(Request $request, Server $server, Backup $backup): array { @@ -156,11 +146,12 @@ public function delete(Request $request, Server $server, Backup $backup): JsonRe throw new AuthorizationException(); } - $server->audit(AuditLog::SERVER__BACKUP_DELETED, function (AuditLog $audit) use ($backup) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; + $this->deleteBackupService->handle($backup); - $this->deleteBackupService->handle($backup); - }); + Activity::event('server:backup.delete') + ->subject($backup) + ->property(['name' => $backup->name, 'failed' => !$backup->is_successful]) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -171,7 +162,7 @@ public function delete(Request $request, Server $server, Backup $backup): JsonRe * which the user is redirected to. * * @throws \Throwable - * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws AuthorizationException */ public function download(Request $request, Server $server, Backup $backup): JsonResponse { @@ -184,9 +175,8 @@ public function download(Request $request, Server $server, Backup $backup): Json } $url = $this->downloadLinkService->handle($backup, $request->user()); - $server->audit(AuditLog::SERVER__BACKUP_DOWNLOADED, function (AuditLog $audit) use ($backup) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; - }); + + Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log(); return new JsonResponse([ 'object' => 'signed_url', @@ -199,18 +189,14 @@ public function download(Request $request, Server $server, Backup $backup): Json * to begin the process of finding (or downloading) the backup and unpacking it * over the server files. * - * If the "truncate" flag is passed through in this request then all of the + * If the "truncate" flag is passed through in this request then all the * files that currently exist on the server will be deleted before restoring. - * Otherwise the archive will simply be unpacked over the existing files. + * Otherwise, the archive will simply be unpacked over the existing files. * * @throws \Throwable */ - public function restore(Request $request, Server $server, Backup $backup): JsonResponse + public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse { - if (!$request->user()->can(Permission::ACTION_BACKUP_RESTORE, $server)) { - throw new AuthorizationException(); - } - // Cannot restore a backup unless a server is fully installed and not currently // processing a different backup restoration request. if (!is_null($server->status)) { @@ -221,9 +207,11 @@ public function restore(Request $request, Server $server, Backup $backup): JsonR throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); } - $server->audit(AuditLog::SERVER__BACKUP_RESTORE_STARTED, function (AuditLog $audit, Server $server) use ($backup, $request) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; + $log = Activity::event('server:backup.restore') + ->subject($backup) + ->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]); + $log->transaction(function () use ($backup, $server, $request) { // If the backup is for an S3 file we need to generate a unique Download link for // it that will allow Wings to actually access the file. if ($backup->disk === Backup::ADAPTER_AWS_S3) { diff --git a/app/Http/Controllers/Api/Client/Servers/CommandController.php b/app/Http/Controllers/Api/Client/Servers/CommandController.php index f9663138c8..eb6394ce53 100644 --- a/app/Http/Controllers/Api/Client/Servers/CommandController.php +++ b/app/Http/Controllers/Api/Client/Servers/CommandController.php @@ -4,7 +4,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; -use Psr\Http\Message\ResponseInterface; +use Pterodactyl\Facades\Activity; use GuzzleHttp\Exception\BadResponseException; use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Repositories\Wings\DaemonCommandRepository; @@ -14,25 +14,18 @@ class CommandController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonCommandRepository - */ - private $repository; - /** * CommandController constructor. */ - public function __construct(DaemonCommandRepository $repository) + public function __construct(private DaemonCommandRepository $repository) { parent::__construct(); - - $this->repository = $repository; } /** * Send a command to a running server. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function index(SendCommandRequest $request, Server $server): Response { @@ -42,10 +35,7 @@ public function index(SendCommandRequest $request, Server $server): Response $previous = $exception->getPrevious(); if ($previous instanceof BadResponseException) { - if ( - $previous->getResponse() instanceof ResponseInterface - && $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY - ) { + if ($previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY) { throw new HttpException(Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception); } } @@ -53,6 +43,8 @@ public function index(SendCommandRequest $request, Server $server): Response throw $exception; } + Activity::event('server:console.command')->property('command', $request->input('command'))->log(); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 4f9aed59d0..9468617977 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -5,7 +5,8 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; -use Pterodactyl\Repositories\Eloquent\DatabaseRepository; +use Pterodactyl\Facades\Activity; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Transformers\Api\Client\DatabaseTransformer; use Pterodactyl\Services\Databases\DatabaseManagementService; @@ -18,45 +19,19 @@ class DatabaseController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Databases\DeployServerDatabaseService - */ - private $deployDatabaseService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $managementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - private $passwordService; - /** * DatabaseController constructor. */ public function __construct( - DatabaseManagementService $managementService, - DatabasePasswordService $passwordService, - DatabaseRepository $repository, - DeployServerDatabaseService $deployDatabaseService + private DeployServerDatabaseService $deployDatabaseService, + private DatabaseManagementService $managementService, + private DatabasePasswordService $passwordService, ) { parent::__construct(); - - $this->deployDatabaseService = $deployDatabaseService; - $this->repository = $repository; - $this->managementService = $managementService; - $this->passwordService = $passwordService; } /** - * Return all of the databases that belong to the given server. + * Return all the databases that belong to the given server. */ public function index(GetDatabasesRequest $request, Server $server): array { @@ -74,7 +49,17 @@ public function index(GetDatabasesRequest $request, Server $server): array */ public function store(StoreDatabaseRequest $request, Server $server): array { - $database = $this->deployDatabaseService->handle($server, $request->validated()); + $database = Activity::event('server:database.create')->transaction(function ($log) use ($request, $server) { + if ($server->databases()->lockForUpdate()->count() >= $server->database_limit) { + throw new DisplayException('Cannot create additional databases on this server: limit has been reached.'); + } + + $database = $this->deployDatabaseService->handle($server, $request->validated()); + + $log->subject($database)->property('name', $database->database); + + return $database; + }); return $this->fractal->item($database) ->parseIncludes(['password']) @@ -86,16 +71,16 @@ public function store(StoreDatabaseRequest $request, Server $server): array * Rotates the password for the given server model and returns a fresh instance to * the caller. * - * @return array - * * @throws \Throwable */ - public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database) + public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array { - $this->passwordService->handle($database); - $database->refresh(); + Activity::event('server:database.rotate-password') + ->subject($database) + ->property('name', $database->database) + ->transaction(fn () => $this->passwordService->handle($database)); - return $this->fractal->item($database) + return $this->fractal->item($database->refresh()) ->parseIncludes(['password']) ->transformWith($this->getTransformer(DatabaseTransformer::class)) ->toArray(); @@ -110,6 +95,11 @@ public function delete(DeleteDatabaseRequest $request, Server $server, Database { $this->managementService->delete($database); - return Response::create('', Response::HTTP_NO_CONTENT); + Activity::event('server:database.delete') + ->subject($database) + ->property('name', $database->database) + ->log(); + + return new Response('', Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 9ff503fd7b..d5ee9e70eb 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -4,13 +4,13 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Response; +use Pterodactyl\Enum\JwtScope; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Nodes\NodeJWTService; -use Illuminate\Contracts\Routing\ResponseFactory; use Pterodactyl\Repositories\Wings\DaemonFileRepository; -use Pterodactyl\Transformers\Daemon\FileObjectTransformer; +use Pterodactyl\Transformers\Api\Client\FileObjectTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\PullFileRequest; @@ -26,34 +26,14 @@ class FileController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonFileRepository - */ - private $fileRepository; - - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory - */ - private $responseFactory; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * FileController constructor. */ public function __construct( - ResponseFactory $responseFactory, - NodeJWTService $jwtService, - DaemonFileRepository $fileRepository + private NodeJWTService $jwtService, + private DaemonFileRepository $fileRepository, ) { parent::__construct(); - - $this->fileRepository = $fileRepository; - $this->responseFactory = $responseFactory; - $this->jwtService = $jwtService; } /** @@ -84,6 +64,8 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo config('pterodactyl.files.max_edit_size') ); + Activity::event('server:file.read')->property('file', $request->get('file'))->log(); + return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']); } @@ -91,23 +73,21 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo * Generates a one-time token with a link that the user can use to * download a given file. * - * @return array - * * @throws \Throwable */ - public function download(GetFileContentsRequest $request, Server $server) + public function download(GetFileContentsRequest $request, Server $server): array { - $token = $server->audit(AuditLog::SERVER__FILESYSTEM_DOWNLOAD, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['file' => $request->get('file')]; - - return $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setClaims([ - 'file_path' => rawurldecode($request->get('file')), - 'server_uuid' => $server->uuid, - ]) - ->handle($server->node, $request->user()->id . $server->uuid); - }); + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setUser($request->user()) + ->setClaims([ + 'file_path' => rawurldecode($request->get('file')), + 'server_uuid' => $server->uuid, + ]) + ->setScopes(JwtScope::FileDownload) + ->handle($server->node, $request->user()->id . $server->uuid); + + Activity::event('server:file.download')->property('file', $request->get('file'))->log(); return [ 'object' => 'signed_url', @@ -128,14 +108,9 @@ public function download(GetFileContentsRequest $request, Server $server) */ public function write(WriteFileContentRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'write_content'; - $audit->metadata = ['file' => $request->get('file')]; + $this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent()); - $this->fileRepository - ->setServer($server) - ->putContent($request->get('file'), $request->getContent()); - }); + Activity::event('server:file.write')->property('file', $request->get('file'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -147,14 +122,14 @@ public function write(WriteFileContentRequest $request, Server $server): JsonRes */ public function create(CreateFolderRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'create_folder'; - $audit->metadata = ['file' => $request->input('root', '/') . $request->input('name')]; + $this->fileRepository + ->setServer($server) + ->createDirectory($request->input('name'), $request->input('root', '/')); - $this->fileRepository - ->setServer($server) - ->createDirectory($request->input('name'), $request->input('root', '/')); - }); + Activity::event('server:file.create-directory') + ->property('name', $request->input('name')) + ->property('directory', $request->input('root')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -166,13 +141,14 @@ public function create(CreateFolderRequest $request, Server $server): JsonRespon */ public function rename(RenameFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_RENAME, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository + ->setServer($server) + ->renameFiles($request->input('root'), $request->input('files')); - $this->fileRepository - ->setServer($server) - ->renameFiles($request->input('root'), $request->input('files')); - }); + Activity::event('server:file.rename') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -184,14 +160,11 @@ public function rename(RenameFileRequest $request, Server $server): JsonResponse */ public function copy(CopyFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'copy_file'; - $audit->metadata = ['file' => $request->input('location')]; + $this->fileRepository + ->setServer($server) + ->copyFile($request->input('location')); - $this->fileRepository - ->setServer($server) - ->copyFile($request->input('location')); - }); + Activity::event('server:file.copy')->property('file', $request->input('location'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -201,18 +174,15 @@ public function copy(CopyFileRequest $request, Server $server): JsonResponse */ public function compress(CompressFilesRequest $request, Server $server): array { - $file = $server->audit(AuditLog::SERVER__FILESYSTEM_COMPRESS, function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); - - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $file = $this->fileRepository->setServer($server)->compressFiles( + $request->input('root'), + $request->input('files') + ); - return $this->fileRepository->setServer($server) - ->compressFiles( - $request->input('root'), - $request->input('files') - ); - }); + Activity::event('server:file.compress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return $this->fractal->item($file) ->transformWith($this->getTransformer(FileObjectTransformer::class)) @@ -224,15 +194,17 @@ public function compress(CompressFilesRequest $request, Server $server): array */ public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse { - $file = $server->audit(AuditLog::SERVER__FILESYSTEM_DECOMPRESS, function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); + set_time_limit(300); - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; + $this->fileRepository->setServer($server)->decompressFile( + $request->input('root'), + $request->input('file') + ); - $this->fileRepository->setServer($server) - ->decompressFile($request->input('root'), $request->input('file')); - }); + Activity::event('server:file.decompress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('file')) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -244,15 +216,15 @@ public function decompress(DecompressFilesRequest $request, Server $server): Jso */ public function delete(DeleteFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_DELETE, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository->setServer($server)->deleteFiles( + $request->input('root'), + $request->input('files') + ); - $this->fileRepository->setServer($server) - ->deleteFiles( - $request->input('root'), - $request->input('files') - ); - }); + Activity::event('server:file.delete') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -264,11 +236,10 @@ public function delete(DeleteFileRequest $request, Server $server): JsonResponse */ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse { - $this->fileRepository->setServer($server) - ->chmodFiles( - $request->input('root'), - $request->input('files') - ); + $this->fileRepository->setServer($server)->chmodFiles( + $request->input('root'), + $request->input('files') + ); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -276,17 +247,20 @@ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse /** * Requests that a file be downloaded from a remote location by Wings. * - * @param $request - * * @throws \Throwable */ public function pull(PullFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_PULL, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['directory' => $request->input('directory'), 'url' => $request->input('url')]; + $this->fileRepository->setServer($server)->pull( + $request->input('url'), + $request->input('directory'), + $request->safe(['filename', 'use_header', 'foreground']) + ); - $this->fileRepository->setServer($server)->pull($request->input('url'), $request->input('directory')); - }); + Activity::event('server:file.pull') + ->property('directory', $request->input('directory')) + ->property('url', $request->input('url')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php index 236273c75e..ed86ac7c67 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php @@ -4,6 +4,7 @@ use Carbon\CarbonImmutable; use Pterodactyl\Models\User; +use Pterodactyl\Enum\JwtScope; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Services\Nodes\NodeJWTService; @@ -12,28 +13,19 @@ class FileUploadController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * FileUploadController constructor. */ public function __construct( - NodeJWTService $jwtService + private NodeJWTService $jwtService, ) { parent::__construct(); - - $this->jwtService = $jwtService; } /** - * Returns a url where files can be uploaded to. - * - * @return \Illuminate\Http\JsonResponse + * Returns an url where files can be uploaded to. */ - public function __invoke(UploadFileRequest $request, Server $server) + public function __invoke(UploadFileRequest $request, Server $server): JsonResponse { return new JsonResponse([ 'object' => 'signed_url', @@ -44,17 +36,15 @@ public function __invoke(UploadFileRequest $request, Server $server) } /** - * Returns a url where files can be uploaded to. - * - * @return string + * Returns an url where files can be uploaded to. */ - protected function getUploadUrl(Server $server, User $user) + protected function getUploadUrl(Server $server, User $user): string { $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setClaims([ - 'server_uuid' => $server->uuid, - ]) + ->setUser($user) + ->setClaims(['server_uuid' => $server->uuid]) + ->setScopes(JwtScope::FileUpload) ->handle($server->node, $user->id . $server->uuid); return sprintf( diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 855cb6780e..6f66f428e8 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -4,10 +4,11 @@ use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Allocation; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Repositories\Eloquent\AllocationRepository; use Pterodactyl\Transformers\Api\Client\AllocationTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Services\Allocations\FindAssignableAllocationService; @@ -20,38 +21,19 @@ class NetworkAllocationController extends ClientApiController { /** - * @var \Pterodactyl\Repositories\Eloquent\AllocationRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $serverRepository; - - /** - * @var \Pterodactyl\Services\Allocations\FindAssignableAllocationService - */ - private $assignableAllocationService; - - /** - * NetworkController constructor. + * NetworkAllocationController constructor. */ public function __construct( - AllocationRepository $repository, - ServerRepository $serverRepository, - FindAssignableAllocationService $assignableAllocationService + protected readonly ConnectionInterface $connection, + private FindAssignableAllocationService $assignableAllocationService, + private ServerRepository $serverRepository, ) { parent::__construct(); - - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->assignableAllocationService = $assignableAllocationService; } /** - * Lists all of the allocations available to a server and wether or - * not they are currently assigned as the primary for this server. + * Lists all the allocations available to a server and whether + * they are currently assigned as the primary for this server. */ public function index(GetNetworkRequest $request, Server $server): array { @@ -68,9 +50,16 @@ public function index(GetNetworkRequest $request, Server $server): array */ public function update(UpdateAllocationRequest $request, Server $server, Allocation $allocation): array { - $allocation = $this->repository->update($allocation->id, [ - 'notes' => $request->input('notes'), - ]); + $original = $allocation->notes; + + $allocation->forceFill(['notes' => $request->input('notes')])->save(); + + if ($original !== $allocation->notes) { + Activity::event('server:allocation.notes') + ->subject($allocation) + ->property(['allocation' => $allocation->toString(), 'old' => $original, 'new' => $allocation->notes]) + ->log(); + } return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -87,6 +76,11 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, { $this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]); + Activity::event('server:allocation.primary') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) ->toArray(); @@ -96,15 +90,21 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, * Set the notes for the allocation for a server. *s. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ public function store(NewAllocationRequest $request, Server $server): array { - if ($server->allocations()->count() >= $server->allocation_limit) { - throw new DisplayException('Cannot assign additional allocations to this server: limit has been reached.'); - } + $allocation = Activity::event('server:allocation.create')->transaction(function ($log) use ($server) { + if ($server->allocations()->lockForUpdate()->count() >= $server->allocation_limit) { + throw new DisplayException('Cannot assign additional allocations to this server: limit has been reached.'); + } + + $allocation = $this->assignableAllocationService->handle($server); - $allocation = $this->assignableAllocationService->handle($server); + $log->subject($allocation)->property('allocation', $allocation->toString()); + + return $allocation; + }); return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -114,12 +114,16 @@ public function store(NewAllocationRequest $request, Server $server): array /** * Delete an allocation from a server. * - * @return \Illuminate\Http\JsonResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation) + public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation): JsonResponse { + // Don't allow the deletion of allocations if the server does not have an + // allocation limit set. + if (empty($server->allocation_limit)) { + throw new DisplayException('You cannot delete allocations for this server: no allocation limit is set.'); + } + if ($allocation->id === $server->allocation_id) { throw new DisplayException('You cannot delete the primary allocation for this server.'); } @@ -129,6 +133,11 @@ public function delete(DeleteAllocationRequest $request, Server $server, Allocat 'server_id' => null, ]); + Activity::event('server:allocation.delete') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/PowerController.php b/app/Http/Controllers/Api/Client/Servers/PowerController.php index 984a1edca7..ca05757651 100644 --- a/app/Http/Controllers/Api/Client/Servers/PowerController.php +++ b/app/Http/Controllers/Api/Client/Servers/PowerController.php @@ -4,31 +4,23 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\SendPowerRequest; class PowerController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository - */ - private $repository; - /** * PowerController constructor. */ - public function __construct(DaemonPowerRepository $repository) + public function __construct(private DaemonPowerRepository $repository) { parent::__construct(); - - $this->repository = $repository; } /** * Send a power action to a server. - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function index(SendPowerRequest $request, Server $server): Response { @@ -36,6 +28,8 @@ public function index(SendPowerRequest $request, Server $server): Response $request->input('signal') ); + Activity::event(strtolower("server:power.{$request->input('signal')}"))->log(); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index d28622392b..dcaf481156 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -12,19 +12,12 @@ class ResourceUtilizationController extends ClientApiController { - private DaemonServerRepository $repository; - - private Repository $cache; - /** * ResourceUtilizationController constructor. */ - public function __construct(Repository $cache, DaemonServerRepository $repository) + public function __construct(private Repository $cache, private DaemonServerRepository $repository) { parent::__construct(); - - $this->cache = $cache; - $this->repository = $repository; } /** @@ -36,7 +29,7 @@ public function __construct(Repository $cache, DaemonServerRepository $repositor */ public function __invoke(GetServerRequest $request, Server $server): array { - $key = "resources:{$server->uuid}"; + $key = "resources:$server->uuid"; $stats = $this->cache->remember($key, Carbon::now()->addSeconds(20), function () use ($server) { return $this->repository->setServer($server)->getDetails(); }); diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 0bbc6bd733..832e426e2b 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -2,13 +2,13 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; -use Exception; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Helpers\Utilities; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; @@ -24,36 +24,20 @@ class ScheduleController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ScheduleRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Schedules\ProcessScheduleService - */ - private $service; - /** * ScheduleController constructor. */ - public function __construct(ScheduleRepository $repository, ProcessScheduleService $service) + public function __construct(private ScheduleRepository $repository, private ProcessScheduleService $service) { parent::__construct(); - - $this->repository = $repository; - $this->service = $service; } /** - * Returns all of the schedules belonging to a given server. - * - * @return array + * Returns all the schedules belonging to a given server. */ - public function index(ViewScheduleRequest $request, Server $server) + public function index(ViewScheduleRequest $request, Server $server): array { - $schedules = $server->schedule; - $schedules->loadMissing('tasks'); + $schedules = $server->schedules->loadMissing('tasks'); return $this->fractal->collection($schedules) ->transformWith($this->getTransformer(ScheduleTransformer::class)) @@ -63,14 +47,12 @@ public function index(ViewScheduleRequest $request, Server $server) /** * Store a new schedule for a server. * - * @return array - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(StoreScheduleRequest $request, Server $server) + public function store(StoreScheduleRequest $request, Server $server): array { - /** @var \Pterodactyl\Models\Schedule $model */ + /** @var Schedule $model */ $model = $this->repository->create([ 'server_id' => $server->id, 'name' => $request->input('name'), @@ -84,6 +66,11 @@ public function store(StoreScheduleRequest $request, Server $server) 'next_run_at' => $this->getNextRunAt($request), ]); + Activity::event('server:schedule.create') + ->subject($model) + ->property('name', $model->name) + ->log(); + return $this->fractal->item($model) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -91,10 +78,8 @@ public function store(StoreScheduleRequest $request, Server $server) /** * Returns a specific schedule for the server. - * - * @return array */ - public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule) + public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule): array { if ($schedule->server_id !== $server->id) { throw new NotFoundHttpException(); @@ -110,13 +95,11 @@ public function view(ViewScheduleRequest $request, Server $server, Schedule $sch /** * Updates a given schedule with the new data provided. * - * @return array - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateScheduleRequest $request, Server $server, Schedule $schedule) + public function update(UpdateScheduleRequest $request, Server $server, Schedule $schedule): array { $active = (bool) $request->input('is_active'); @@ -142,6 +125,11 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule $this->repository->update($schedule->id, $data); + Activity::event('server:schedule.update') + ->subject($schedule) + ->property(['name' => $schedule->name, 'active' => $active]) + ->log(); + return $this->fractal->item($schedule->refresh()) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -151,33 +139,33 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule * Executes a given schedule immediately rather than waiting on it's normally scheduled time * to pass. This does not care about the schedule state. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule) + public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule): JsonResponse { $this->service->handle($schedule, true); + Activity::event('server:schedule.execute')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], JsonResponse::HTTP_ACCEPTED); } /** * Deletes a schedule and it's associated tasks. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule) + public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule): JsonResponse { $this->repository->delete($schedule->id); + Activity::event('server:schedule.delete')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** * Get the next run timestamp based on the cron data provided. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ protected function getNextRunAt(Request $request): Carbon { @@ -189,7 +177,7 @@ protected function getNextRunAt(Request $request): Carbon $request->input('month'), $request->input('day_of_week') ); - } catch (Exception $exception) { + } catch (\Exception $exception) { throw new DisplayException('The cron data provided does not evaluate to a valid expression.'); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 4ceed6550c..1091fb2cae 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -7,7 +7,9 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Transformers\Api\Client\TaskTransformer; @@ -19,53 +21,71 @@ class ScheduleTaskController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\TaskRepository - */ - private $repository; - /** * ScheduleTaskController constructor. */ - public function __construct(TaskRepository $repository) - { + public function __construct( + private ConnectionInterface $connection, + private TaskRepository $repository, + ) { parent::__construct(); - - $this->repository = $repository; } /** * Create a new task for a given schedule and store it in the database. * - * @return array - * - * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\ServiceLimitExceededException + * @throws ServiceLimitExceededException */ - public function store(StoreTaskRequest $request, Server $server, Schedule $schedule) + public function store(StoreTaskRequest $request, Server $server, Schedule $schedule): array { $limit = config('pterodactyl.client_features.schedules.per_schedule_task_limit', 10); if ($schedule->tasks()->count() >= $limit) { - throw new ServiceLimitExceededException("Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit."); + throw new ServiceLimitExceededException("Schedules may not have more than $limit tasks associated with them. Creating this task would put this schedule over the limit."); } if ($server->backup_limit === 0 && $request->action === 'backup') { throw new HttpForbiddenException("A backup task cannot be created when the server's backup limit is set to 0."); } - /** @var \Pterodactyl\Models\Task|null $lastTask */ + /** @var Task|null $lastTask */ $lastTask = $schedule->tasks()->orderByDesc('sequence_id')->first(); - /** @var \Pterodactyl\Models\Task $task */ - $task = $this->repository->create([ - 'schedule_id' => $schedule->id, - 'sequence_id' => ($lastTask->sequence_id ?? 0) + 1, - 'action' => $request->input('action'), - 'payload' => $request->input('payload') ?? '', - 'time_offset' => $request->input('time_offset'), - 'continue_on_failure' => (bool) $request->input('continue_on_failure'), - ]); + /** @var Task $task */ + $task = $this->connection->transaction(function () use ($request, $schedule, $lastTask) { + $sequenceId = ($lastTask->sequence_id ?? 0) + 1; + $requestSequenceId = $request->integer('sequence_id', $sequenceId); + + // Ensure that the sequence id is at least 1. + if ($requestSequenceId < 1) { + $requestSequenceId = 1; + } + + // If the sequence id from the request is greater than or equal to the next available + // sequence id, we don't need to do anything special. Otherwise, we need to update + // the sequence id of all tasks that are greater than or equal to the request sequence + // id to be one greater than the current value. + if ($requestSequenceId < $sequenceId) { + $schedule->tasks() + ->where('sequence_id', '>=', $requestSequenceId) + ->increment('sequence_id'); + $sequenceId = $requestSequenceId; + } + + return $this->repository->create([ + 'schedule_id' => $schedule->id, + 'sequence_id' => $sequenceId, + 'action' => $request->input('action'), + 'payload' => $request->input('payload') ?? '', + 'time_offset' => $request->input('time_offset'), + 'continue_on_failure' => $request->boolean('continue_on_failure'), + ]); + }); + + Activity::event('server:task.create') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); return $this->fractal->item($task) ->transformWith($this->getTransformer(TaskTransformer::class)) @@ -75,13 +95,10 @@ public function store(StoreTaskRequest $request, Server $server, Schedule $sched /** * Updates a given task for a server. * - * @return array - * - * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(StoreTaskRequest $request, Server $server, Schedule $schedule, Task $task) + public function update(StoreTaskRequest $request, Server $server, Schedule $schedule, Task $task): array { if ($schedule->id !== $task->schedule_id || $server->id !== $schedule->server_id) { throw new NotFoundHttpException(); @@ -91,12 +108,39 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche throw new HttpForbiddenException("A backup task cannot be created when the server's backup limit is set to 0."); } - $this->repository->update($task->id, [ - 'action' => $request->input('action'), - 'payload' => $request->input('payload') ?? '', - 'time_offset' => $request->input('time_offset'), - 'continue_on_failure' => (bool) $request->input('continue_on_failure'), - ]); + $this->connection->transaction(function () use ($request, $schedule, $task) { + $sequenceId = $request->integer('sequence_id', $task->sequence_id); + // Ensure that the sequence id is at least 1. + if ($sequenceId < 1) { + $sequenceId = 1; + } + + // Shift all other tasks in the schedule up or down to make room for the new task. + if ($sequenceId < $task->sequence_id) { + $schedule->tasks() + ->where('sequence_id', '>=', $sequenceId) + ->where('sequence_id', '<', $task->sequence_id) + ->increment('sequence_id'); + } elseif ($sequenceId > $task->sequence_id) { + $schedule->tasks() + ->where('sequence_id', '>', $task->sequence_id) + ->where('sequence_id', '<=', $sequenceId) + ->decrement('sequence_id'); + } + + $this->repository->update($task->id, [ + 'sequence_id' => $sequenceId, + 'action' => $request->input('action'), + 'payload' => $request->input('payload') ?? '', + 'time_offset' => $request->input('time_offset'), + 'continue_on_failure' => $request->boolean('continue_on_failure'), + ]); + }); + + Activity::event('server:task.update') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); return $this->fractal->item($task->refresh()) ->transformWith($this->getTransformer(TaskTransformer::class)) @@ -107,11 +151,9 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche * Delete a given task for a schedule. If there are subsequent tasks stored in the database * for this schedule their sequence IDs are decremented properly. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Exception */ - public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task) + public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task): JsonResponse { if ($task->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { throw new NotFoundHttpException(); @@ -121,12 +163,13 @@ public function delete(ClientApiRequest $request, Server $server, Schedule $sche throw new HttpForbiddenException('You do not have permission to perform this action.'); } - $schedule->tasks()->where('sequence_id', '>', $task->sequence_id)->update([ - 'sequence_id' => $schedule->tasks()->getConnection()->raw('(sequence_id - 1)'), - ]); - + $schedule->tasks() + ->where('sequence_id', '>', $task->sequence_id) + ->decrement('sequence_id'); $task->delete(); + Activity::event('server:task.delete')->subject($schedule, $task)->property('name', $schedule->name)->log(); + return new JsonResponse(null, Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index 82091c48b0..63eb9b9889 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; -use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -11,25 +10,12 @@ class ServerController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService - */ - private $permissionsService; - /** * ServerController constructor. */ - public function __construct(GetUserPermissionsService $permissionsService, SubuserRepository $repository) + public function __construct(private GetUserPermissionsService $permissionsService) { parent::__construct(); - - $this->repository = $repository; - $this->permissionsService = $permissionsService; } /** diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 110cd0868f..b77678889f 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ReinstallServerService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -15,75 +16,80 @@ class SettingsController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - private $reinstallServerService; - /** * SettingsController constructor. */ public function __construct( - ServerRepository $repository, - ReinstallServerService $reinstallServerService + private ServerRepository $repository, + private ReinstallServerService $reinstallServerService, ) { parent::__construct(); - - $this->repository = $repository; - $this->reinstallServerService = $reinstallServerService; } /** * Renames a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function rename(RenameServerRequest $request, Server $server) + public function rename(RenameServerRequest $request, Server $server): JsonResponse { + $name = $request->input('name'); + $description = $request->has('description') ? (string) $request->input('description') : $server->description; $this->repository->update($server->id, [ - 'name' => $request->input('name'), + 'name' => $name, + 'description' => $description, ]); + if ($server->name !== $name) { + Activity::event('server:settings.rename') + ->property(['old' => $server->name, 'new' => $name]) + ->log(); + } + + if ($server->description !== $description) { + Activity::event('server:settings.description') + ->property(['old' => $server->description, 'new' => $description]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** * Reinstalls the server on the daemon. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function reinstall(ReinstallServerRequest $request, Server $server) + public function reinstall(ReinstallServerRequest $request, Server $server): JsonResponse { $this->reinstallServerService->handle($server); + Activity::event('server:reinstall')->log(); + return new JsonResponse([], Response::HTTP_ACCEPTED); } /** * Changes the Docker image in use by the server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function dockerImage(SetDockerImageRequest $request, Server $server) + public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse { - if (!in_array($server->image, $server->egg->docker_images)) { + if (!in_array($server->image, array_values($server->egg->docker_images))) { throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.'); } + $original = $server->image; $server->forceFill(['image' => $request->input('docker_image')])->saveOrFail(); + if ($original !== $server->image) { + Activity::event('server:startup.image') + ->property(['old' => $original, 'new' => $request->input('docker_image')]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 06b4a5066e..2f8cd6c6ae 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Servers\StartupCommandService; -use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Transformers\Api\Client\EggVariableTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -14,41 +14,22 @@ class StartupController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $service; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\StartupCommandService - */ - private $startupCommandService; - /** * StartupController constructor. */ - public function __construct(VariableValidatorService $service, StartupCommandService $startupCommandService, ServerVariableRepository $repository) - { + public function __construct( + private StartupCommandService $startupCommandService, + private ServerVariableRepository $repository, + ) { parent::__construct(); - - $this->service = $service; - $this->repository = $repository; - $this->startupCommandService = $startupCommandService; } /** - * Returns the startup information for the server including all of the variables. - * - * @return array + * Returns the startup information for the server including all the variables. */ - public function index(GetStartupRequest $request, Server $server) + public function index(GetStartupRequest $request, Server $server): array { - $startup = $this->startupCommandService->handle($server, false); + $startup = $this->startupCommandService->handle($server); return $this->fractal->collection( $server->variables()->where('user_viewable', true)->get() @@ -65,15 +46,12 @@ public function index(GetStartupRequest $request, Server $server) /** * Updates a single variable for a server. * - * @return array - * * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateStartupVariableRequest $request, Server $server) + public function update(UpdateStartupVariableRequest $request, Server $server): array { - /** @var \Pterodactyl\Models\EggVariable $variable */ $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); if (is_null($variable) || !$variable->user_viewable) { @@ -82,6 +60,8 @@ public function update(UpdateStartupVariableRequest $request, Server $server) throw new BadRequestHttpException('The environment variable you are trying to edit is read-only.'); } + $original = $variable->server_value; + // Revalidate the variable value using the egg variable specific validation rules for it. $this->validate($request, ['value' => $variable->rules]); @@ -95,7 +75,18 @@ public function update(UpdateStartupVariableRequest $request, Server $server) $variable = $variable->refresh(); $variable->server_value = $request->input('value'); - $startup = $this->startupCommandService->handle($server, false); + $startup = $this->startupCommandService->handle($server); + + if ($original !== $request->input('value')) { + Activity::event('server:startup.edit') + ->subject($variable) + ->property([ + 'variable' => $variable->env_variable, + 'old' => $original, + 'new' => $request->input('value') ?? '', + ]) + ->log(); + } return $this->fractal->item($variable) ->transformWith($this->getTransformer(EggVariableTransformer::class)) diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 68f32cf8bc..b37cc8e8d3 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -5,14 +5,14 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; -use Illuminate\Support\Facades\Log; +use Pterodactyl\Jobs\RevokeSftpAccessJob; use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Services\Subusers\SubuserCreationService; -use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Transformers\Api\Client\SubuserTransformer; +use Pterodactyl\Repositories\Wings\DaemonRevocationRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest; @@ -20,42 +20,21 @@ class SubuserController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Subusers\SubuserCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * SubuserController constructor. */ public function __construct( - SubuserRepository $repository, - SubuserCreationService $creationService, - DaemonServerRepository $serverRepository + private SubuserRepository $repository, + private SubuserCreationService $creationService, + private DaemonRevocationRepository $revocationRepository, ) { parent::__construct(); - - $this->repository = $repository; - $this->creationService = $creationService; - $this->serverRepository = $serverRepository; } /** * Return the users associated with this server instance. - * - * @return array */ - public function index(GetSubuserRequest $request, Server $server) + public function index(GetSubuserRequest $request, Server $server): array { return $this->fractal->collection($server->subusers) ->transformWith($this->getTransformer(SubuserTransformer::class)) @@ -64,10 +43,8 @@ public function index(GetSubuserRequest $request, Server $server) /** * Returns a single subuser associated with this server instance. - * - * @return array */ - public function view(GetSubuserRequest $request) + public function view(GetSubuserRequest $request): array { $subuser = $request->attributes->get('subuser'); @@ -79,14 +56,12 @@ public function view(GetSubuserRequest $request) /** * Create a new subuser for the given server. * - * @return array - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException * @throws \Throwable */ - public function store(StoreSubuserRequest $request, Server $server) + public function store(StoreSubuserRequest $request, Server $server): array { $response = $this->creationService->handle( $server, @@ -94,6 +69,11 @@ public function store(StoreSubuserRequest $request, Server $server) $this->getDefaultPermissions($request) ); + Activity::event('server:subuser.create') + ->subject($response->user) + ->property(['email' => $request->input('email'), 'permissions' => $this->getDefaultPermissions($request)]) + ->log(); + return $this->fractal->item($response) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -116,22 +96,29 @@ public function update(UpdateSubuserRequest $request, Server $server): array sort($permissions); sort($current); + $log = Activity::event('server:subuser.update') + ->subject($subuser->user) + ->property([ + 'email' => $subuser->user->email, + 'old' => $current, + 'new' => $permissions, + 'revoked' => true, + ]); + // Only update the database and hit up the Wings instance to invalidate JTI's if the permissions // have actually changed for the user. if ($permissions !== $current) { - $this->repository->update($subuser->id, [ - 'permissions' => $this->getDefaultPermissions($request), - ]); + $log->transaction(function () use ($request, $subuser, $server) { + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. Chances are it is - // offline in this event and the token will be invalid anyways once Wings boots back. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + RevokeSftpAccessJob::dispatch($subuser->user->uuid, $server); + }); } + $log->reset(); + return $this->fractal->item($subuser->refresh()) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -139,31 +126,44 @@ public function update(UpdateSubuserRequest $request, Server $server): array /** * Removes a subusers from a server's assignment. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteSubuserRequest $request, Server $server) + public function delete(DeleteSubuserRequest $request, Server $server): JsonResponse { /** @var \Pterodactyl\Models\Subuser $subuser */ $subuser = $request->attributes->get('subuser'); - $this->repository->delete($subuser->id); + $log = Activity::event('server:subuser.delete') + ->subject($subuser->user) + ->property('email', $subuser->user->email) + ->property('revoked', true); - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + $log->transaction(function () use ($server, $subuser) { + $subuser->delete(); + + RevokeSftpAccessJob::dispatch($subuser->user->uuid, $server); + }); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } /** - * Returns the default permissions for all subusers to ensure none are ever removed wrongly. + * Returns the default permissions for subusers and parses out any permissions + * that were passed that do not also exist in the internally tracked list of + * permissions. */ protected function getDefaultPermissions(Request $request): array { - return array_unique(array_merge($request->input('permissions') ?? [], [Permission::ACTION_WEBSOCKET_CONNECT])); + $allowed = Permission::permissions() + ->map(function ($value, $prefix) { + return array_map(function ($value) use ($prefix) { + return "$prefix.$value"; + }, array_keys($value['keys'])); + }) + ->flatten() + ->all(); + + $cleaned = array_intersect($request->input('permissions') ?? [], $allowed); + + return array_unique(array_merge($cleaned, [Permission::ACTION_WEBSOCKET_CONNECT])); } } diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 08abee27df..a949bd0a72 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; +use Pterodactyl\Enum\JwtScope; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; @@ -14,38 +15,23 @@ class WebsocketController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - - /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService - */ - private $permissionsService; - /** * WebsocketController constructor. */ public function __construct( - NodeJWTService $jwtService, - GetUserPermissionsService $permissionsService + private NodeJWTService $jwtService, + private GetUserPermissionsService $permissionsService, ) { parent::__construct(); - - $this->jwtService = $jwtService; - $this->permissionsService = $permissionsService; } /** * Generates a one-time token that is sent along in every websocket call to the Daemon. - * This is a signed JWT that the Daemon then uses the verify the user's identity, and - * allows us to continually renew this token and avoid users mainitaining sessions wrongly, + * This is a signed JWT that the Daemon then uses to verify the user's identity, and + * allows us to continually renew this token and avoid users maintaining sessions wrongly, * as well as ensure that user's only perform actions they're allowed to. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(ClientApiRequest $request, Server $server) + public function __invoke(ClientApiRequest $request, Server $server): JsonResponse { $user = $request->user(); if ($user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $server)) { @@ -69,11 +55,12 @@ public function __invoke(ClientApiRequest $request, Server $server) $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(10)) + ->setUser($request->user()) ->setClaims([ - 'user_id' => $request->user()->id, 'server_uuid' => $server->uuid, 'permissions' => $permissions, ]) + ->setScopes(JwtScope::Websocket) ->handle($node, $user->id . $server->uuid); $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $node->getConnectionAddress()); diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index b14f9d4bc4..edc13d26f7 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -6,42 +6,23 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Validation\ValidationException; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Users\TwoFactorSetupService; use Pterodactyl\Services\Users\ToggleTwoFactorService; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class TwoFactorController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Users\TwoFactorSetupService - */ - private $setupService; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validation; - - /** - * @var \Pterodactyl\Services\Users\ToggleTwoFactorService - */ - private $toggleTwoFactorService; - /** * TwoFactorController constructor. */ public function __construct( - ToggleTwoFactorService $toggleTwoFactorService, - TwoFactorSetupService $setupService, - Factory $validation + private ToggleTwoFactorService $toggleTwoFactorService, + private TwoFactorSetupService $setupService, + private ValidationFactory $validation, ) { parent::__construct(); - - $this->setupService = $setupService; - $this->validation = $validation; - $this->toggleTwoFactorService = $toggleTwoFactorService; } /** @@ -49,12 +30,10 @@ public function __construct( * it on their account. If two-factor is already enabled this endpoint * will return a 400 error. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(Request $request) + public function index(Request $request): JsonResponse { if ($request->user()->use_totp) { throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.'); @@ -68,26 +47,24 @@ public function index(Request $request) /** * Updates a user's account to have two-factor enabled. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable * @throws \Illuminate\Validation\ValidationException - * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException - * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException - * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException - * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid */ - public function store(Request $request) + public function store(Request $request): JsonResponse { $validator = $this->validation->make($request->all(), [ - 'code' => 'required|string', + 'code' => ['required', 'string', 'size:6'], + 'password' => ['required', 'string'], ]); - if ($validator->fails()) { - throw new ValidationException($validator); + $data = $validator->validate(); + if (!password_verify($data['password'], $request->user()->password)) { + throw new BadRequestHttpException('The password provided was not valid.'); } - $tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); + $tokens = $this->toggleTwoFactorService->handle($request->user(), $data['code'], true); + + Activity::event('user:two-factor.create')->log(); return new JsonResponse([ 'object' => 'recovery_tokens', @@ -101,9 +78,9 @@ public function store(Request $request) * Disables two-factor authentication on an account if the password provided * is valid. * - * @return \Illuminate\Http\JsonResponse + * @throws \Throwable */ - public function delete(Request $request) + public function delete(Request $request): JsonResponse { if (!password_verify($request->input('password') ?? '', $request->user()->password)) { throw new BadRequestHttpException('The password provided was not valid.'); @@ -117,6 +94,8 @@ public function delete(Request $request) 'use_totp' => false, ]); + Activity::event('user:two-factor.delete')->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Remote/ActivityProcessingController.php b/app/Http/Controllers/Api/Remote/ActivityProcessingController.php new file mode 100644 index 0000000000..b88d7ae7a7 --- /dev/null +++ b/app/Http/Controllers/Api/Remote/ActivityProcessingController.php @@ -0,0 +1,90 @@ +getTimezone(); + + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + + $servers = $node->servers()->whereIn('uuid', $request->servers())->get()->keyBy('uuid'); + $users = User::query()->whereIn('uuid', $request->users())->get()->keyBy('uuid'); + + $logs = []; + foreach ($request->input('data') as $datum) { + /** @var Server|null $server */ + $server = $servers->get($datum['server']); + if (is_null($server) || !Str::startsWith($datum['event'], 'server:')) { + continue; + } + + try { + $when = Carbon::createFromFormat( + \DateTimeInterface::RFC3339, + preg_replace('/(\.\d+)Z$/', 'Z', $datum['timestamp']), + 'UTC' + ); + } catch (\Exception $exception) { + Log::warning($exception, ['timestamp' => $datum['timestamp']]); + + // If we cannot parse the value for some reason don't blow up this request, just go ahead + // and log the event with the current time, and set the metadata value to have the original + // timestamp that was provided. + $when = Carbon::now(); + $datum['metadata'] = array_merge($datum['metadata'] ?? [], ['original_timestamp' => $datum['timestamp']]); + } + + $log = [ + 'ip' => empty($datum['ip']) ? '127.0.0.1' : $datum['ip'], + 'event' => $datum['event'], + 'properties' => json_encode($datum['metadata'] ?? []), + // We have to change the time to the current timezone due to the way Laravel is handling + // the date casting internally. If we just leave it in UTC it ends up getting double-cast + // and the time is way off. + 'timestamp' => $when->setTimezone($tz), + ]; + + if ($user = $users->get($datum['user'])) { + $log['actor_id'] = $user->id; + $log['actor_type'] = $user->getMorphClass(); + } + + if (!isset($logs[$datum['server']])) { + $logs[$datum['server']] = []; + } + + $logs[$datum['server']][] = $log; + } + + foreach ($logs as $key => $data) { + Assert::isInstanceOf($server = $servers->get($key), Server::class); + + $batch = []; + foreach ($data as $datum) { + $id = ActivityLog::insertGetId($datum); + $batch[] = [ + 'activity_log_id' => $id, + 'subject_id' => $server->id, + 'subject_type' => $server->getMorphClass(), + ]; + } + + ActivityLogSubject::insert($batch); + } + } +} diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php index 042486c248..6d8865fd21 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php @@ -6,70 +6,66 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Backup; use Illuminate\Http\JsonResponse; -use League\Flysystem\AwsS3v3\AwsS3Adapter; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Backups\BackupManager; -use Pterodactyl\Repositories\Eloquent\BackupRepository; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class BackupRemoteUploadController extends Controller { - public const PART_SIZE = 5 * 1024 * 1024 * 1024; - - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; + public const DEFAULT_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024; /** * BackupRemoteUploadController constructor. */ - public function __construct(BackupRepository $repository, BackupManager $backupManager) + public function __construct(private BackupManager $backupManager) { - $this->repository = $repository; - $this->backupManager = $backupManager; } /** * Returns the required presigned urls to upload a backup to S3 cloud storage. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Exception * @throws \Throwable * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public function __invoke(Request $request, string $backup) + public function __invoke(Request $request, string $backup): JsonResponse { + // Get the node associated with the request. + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + // Get the size query parameter. $size = (int) $request->query('size'); if (empty($size)) { throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.'); } - /** @var \Pterodactyl\Models\Backup $backup */ - $backup = Backup::query()->where('uuid', $backup)->firstOrFail(); + $model = Backup::query()->where('uuid', $backup)->firstOrFail(); + + // Check that the backup is "owned" by the node making the request. This avoids other nodes + // from messing with backups that they don't own. + $server = $model->server; + if ($server->node_id !== $node->id) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } // Prevent backups that have already been completed from trying to // be uploaded again. - if (!is_null($backup->completed_at)) { + if (!is_null($model->completed_at)) { throw new ConflictHttpException('This backup is already in a completed state.'); } // Ensure we are using the S3 adapter. $adapter = $this->backupManager->adapter(); - if (!$adapter instanceof AwsS3Adapter) { + if (!$adapter instanceof S3Filesystem) { throw new BadRequestHttpException('The configured backup adapter is not an S3 compatible adapter.'); } // The path where backup will be uploaded to - $path = sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid); + $path = sprintf('%s/%s.tar.gz', $model->server->uuid, $model->uuid); // Get the S3 client $client = $adapter->getClient(); @@ -82,6 +78,11 @@ public function __invoke(Request $request, string $backup) 'ContentType' => 'application/x-gzip', ]; + $storageClass = config('backups.disks.s3.storage_class'); + if (!is_null($storageClass)) { + $params['StorageClass'] = $storageClass; + } + // Execute the CreateMultipartUpload request $result = $client->execute($client->getCommand('CreateMultipartUpload', $params)); @@ -89,9 +90,12 @@ public function __invoke(Request $request, string $backup) // the other presigned urls. $params['UploadId'] = $result->get('UploadId'); + // Retrieve configured part size + $maxPartSize = $this->getConfiguredMaxPartSize(); + // Create as many UploadPart presigned urls as needed $parts = []; - for ($i = 0; $i < ($size / self::PART_SIZE); ++$i) { + for ($i = 0; $i < ($size / $maxPartSize); ++$i) { $parts[] = $client->createPresignedRequest( $client->getCommand('UploadPart', array_merge($params, ['PartNumber' => $i + 1])), $expires @@ -99,11 +103,32 @@ public function __invoke(Request $request, string $backup) } // Set the upload_id on the backup in the database. - $backup->update(['upload_id' => $params['UploadId']]); + $model->update(['upload_id' => $params['UploadId']]); return new JsonResponse([ 'parts' => $parts, - 'part_size' => self::PART_SIZE, + 'part_size' => $maxPartSize, ]); } + + /** + * Get the configured maximum size of a single part in the multipart upload. + * + * The function tries to retrieve a configured value from the configuration. + * If no value is specified, a fallback value will be used. + * + * Note if the received config cannot be converted to int (0), is zero or is negative, + * the fallback value will be used too. + * + * The fallback value is {@see BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE}. + */ + private function getConfiguredMaxPartSize(): int + { + $maxPartSize = (int) config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE); + if ($maxPartSize <= 0) { + $maxPartSize = self::DEFAULT_MAX_PART_SIZE; + } + + return $maxPartSize; + } } diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index f4e7bd6eab..aede7fae12 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -5,56 +5,59 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Request; use Pterodactyl\Models\Backup; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; -use League\Flysystem\AwsS3v3\AwsS3Adapter; +use Pterodactyl\Facades\Activity; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Backups\BackupManager; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest; class BackupStatusController extends Controller { - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - /** * BackupStatusController constructor. */ - public function __construct(BackupManager $backupManager) + public function __construct(private BackupManager $backupManager) { - $this->backupManager = $backupManager; } /** * Handles updating the state of a backup. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function index(ReportBackupCompleteRequest $request, string $backup) + public function index(ReportBackupCompleteRequest $request, string $backup): JsonResponse { - /** @var \Pterodactyl\Models\Backup $model */ - $model = Backup::query()->where('uuid', $backup)->firstOrFail(); + // Get the node associated with the request. + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + + /** @var Backup $model */ + $model = Backup::query() + ->where('uuid', $backup) + ->firstOrFail(); + + // Check that the backup is "owned" by the node making the request. This avoids other nodes + // from messing with backups that they don't own. + /** @var \Pterodactyl\Models\Server $server */ + $server = $model->server; + if ($server->node_id !== $node->id) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } if ($model->is_successful) { throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); } - $action = $request->input('successful') - ? AuditLog::SERVER__BACKUP_COMPELTED - : AuditLog::SERVER__BACKUP_FAILED; - - $model->server->audit($action, function (AuditLog $audit) use ($model, $request) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $model->uuid]; + $action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.fail'; + $log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name); + $log->transaction(function () use ($model, $request) { $successful = $request->boolean('successful'); + $model->fill([ 'is_successful' => $successful, // Change the lock state to unlocked if this was a failed backup so that it can be @@ -69,8 +72,8 @@ public function index(ReportBackupCompleteRequest $request, string $backup) // Check if we are using the s3 backup adapter. If so, make sure we mark the backup as // being completed in S3 correctly. $adapter = $this->backupManager->adapter(); - if ($adapter instanceof AwsS3Adapter) { - $this->completeMultipartUpload($model, $adapter, $successful); + if ($adapter instanceof S3Filesystem) { + $this->completeMultipartUpload($model, $adapter, $successful, $request->input('parts')); } }); @@ -85,47 +88,47 @@ public function index(ReportBackupCompleteRequest $request, string $backup) * The only thing the successful field does is update the entry value for the audit logs * table tracking for this restoration. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function restore(Request $request, string $backup) + public function restore(Request $request, string $backup): JsonResponse { - /** @var \Pterodactyl\Models\Backup $model */ + /** @var Backup $model */ $model = Backup::query()->where('uuid', $backup)->firstOrFail(); - $action = $request->get('successful') - ? AuditLog::SERVER__BACKUP_RESTORE_COMPLETED - : AuditLog::SERVER__BACKUP_RESTORE_FAILED; - - // Just create a new audit entry for this event and update the server state - // so that power actions, file management, and backups can resume as normal. - $model->server->audit($action, function (AuditLog $audit, Server $server) use ($backup) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $backup]; - $server->update(['status' => null]); - }); + + $node = $request->attributes->get('node'); + if (! $model->server->node->is($node)) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } + + $model->server->update(['status' => null]); + + Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed') + ->subject($model, $model->server) + ->property('name', $model->name) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } /** - * Marks a multipart upload in a given S3-compatiable instance as failed or successful for + * Marks a multipart upload in a given S3-compatible instance as failed or successful for * the given backup. * * @throws \Exception - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter, bool $successful) + protected function completeMultipartUpload(Backup $backup, S3Filesystem $adapter, bool $successful, ?array $parts): void { // This should never really happen, but if it does don't let us fall victim to Amazon's // wildly fun error messaging. Just stop the process right here. if (empty($backup->upload_id)) { - // A failed backup doesn't need to error here, this can happen if the backup encouters + // A failed backup doesn't need to error here, this can happen if the backup encounters // an error before we even start the upload. AWS gives you tooling to clear these failed // multipart uploads as needed too. if (!$successful) { return; } + throw new DisplayException('Cannot complete backup request: no upload_id present on model.'); } @@ -144,9 +147,20 @@ protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter // Otherwise send a CompleteMultipartUpload request. $params['MultipartUpload'] = [ - 'Parts' => $client->execute($client->getCommand('ListParts', $params))['Parts'], + 'Parts' => [], ]; + if (is_null($parts)) { + $params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts']; + } else { + foreach ($parts as $part) { + $params['MultipartUpload']['Parts'][] = [ + 'ETag' => $part['etag'], + 'PartNumber' => $part['part_number'], + ]; + } + } + $client->execute($client->getCommand('CompleteMultipartUpload', $params)); } } diff --git a/app/Http/Controllers/Api/Remote/EggInstallController.php b/app/Http/Controllers/Api/Remote/EggInstallController.php index e1f804be23..31df7a96ce 100644 --- a/app/Http/Controllers/Api/Remote/EggInstallController.php +++ b/app/Http/Controllers/Api/Remote/EggInstallController.php @@ -10,23 +10,11 @@ class EggInstallController extends Controller { - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environment; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * EggInstallController constructor. */ - public function __construct(EnvironmentService $environment, ServerRepositoryInterface $repository) + public function __construct(private EnvironmentService $environment, private ServerRepositoryInterface $repository) { - $this->environment = $environment; - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 6b49fafa3e..26e28ae373 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -3,62 +3,55 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; -use Illuminate\Database\Query\Builder; -use Illuminate\Database\Query\JoinClause; +use Pterodactyl\Facades\Activity; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Services\Eggs\EggConfigurationService; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Http\Resources\Wings\ServerConfigurationCollection; use Pterodactyl\Services\Servers\ServerConfigurationStructureService; class ServerDetailsController extends Controller { - /** - * @var \Pterodactyl\Services\Eggs\EggConfigurationService - */ - private $eggConfigurationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - /** * ServerConfigurationController constructor. */ public function __construct( - ServerRepository $repository, - ServerConfigurationStructureService $configurationStructureService, - EggConfigurationService $eggConfigurationService, - NodeRepository $nodeRepository + protected ConnectionInterface $connection, + private ServerRepository $repository, + private ServerConfigurationStructureService $configurationStructureService, + private EggConfigurationService $eggConfigurationService, ) { - $this->eggConfigurationService = $eggConfigurationService; - $this->repository = $repository; - $this->configurationStructureService = $configurationStructureService; } /** * Returns details about the server that allows Wings to self-recover and ensure * that the state of the server matches the Panel at all times. * - * @param string $uuid - * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function __invoke(Request $request, $uuid) + public function __invoke(Request $request, string $uuid): JsonResponse { + Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class); + $server = $this->repository->getByUuid($uuid); + $transfer = $server->transfer; + + // If the server is being transferred allow either node to request information about + // the server. If the server is not being transferred only the target node is allowed + // to fetch these details. + $valid = $transfer + ? $node->id === $transfer->old_node || $node->id === $transfer->new_node + : $node->id === $server->node_id; + + if (! $valid) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } return new JsonResponse([ 'settings' => $this->configurationStructureService->handle($server), @@ -68,15 +61,13 @@ public function __invoke(Request $request, $uuid) /** * Lists all servers with their configurations that are assigned to the requesting node. - * - * @return \Pterodactyl\Http\Resources\Wings\ServerConfigurationCollection */ - public function list(Request $request) + public function list(Request $request): ServerConfigurationCollection { - /** @var \Pterodactyl\Models\Node $node */ + /** @var Node $node */ $node = $request->attributes->get('node'); - // Avoid run-away N+1 SQL queries by pre-loading the relationships that are used + // Avoid run-away N+1 SQL queries by preloading the relationships that are used // within each of the services called below. $servers = Server::query()->with('allocations', 'egg', 'mounts', 'variables', 'location') ->where('node_id', $node->id) @@ -93,58 +84,51 @@ public function list(Request $request) * do not get incorrectly stuck in installing/restoring from backup states since * a Wings reboot would completely stop those processes. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function resetState(Request $request) + public function resetState(Request $request): JsonResponse { $node = $request->attributes->get('node'); - // Get all of the servers that are currently marked as restoring from a backup + // Get all the servers that are currently marked as restoring from a backup // on this node that do not have a failed backup tracked in the audit logs table // as well. // // For each of those servers we'll track a new audit log entry to mark them as // failed and then update them all to be in a valid state. - /** @var \Pterodactyl\Models\Server[] $servers */ $servers = Server::query() - ->select('servers.*') - ->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid') - ->leftJoinSub(function (Builder $builder) { - $builder->select('*')->from('audit_logs') - ->where('action', AuditLog::SERVER__BACKUP_RESTORE_STARTED) - ->orderByDesc('created_at') - ->limit(1); - }, 'started', 'started.server_id', '=', 'servers.id') - ->leftJoin('audit_logs as completed', function (JoinClause $clause) { - $clause->whereColumn('completed.created_at', '>', 'started.created_at') - ->whereIn('completed.action', [ - AuditLog::SERVER__BACKUP_RESTORE_COMPLETED, - AuditLog::SERVER__BACKUP_RESTORE_FAILED, - ]); - }) - ->whereNotNull('started.id') - ->whereNull('completed.id') - ->where('servers.node_id', $node->id) - ->where('servers.status', Server::STATUS_RESTORING_BACKUP) + ->with([ + 'activity' => fn ($builder) => $builder + ->where('activity_logs.event', 'server:backup.restore-started') + ->latest('timestamp'), + ]) + ->where('node_id', $node->id) + ->where('status', Server::STATUS_RESTORING_BACKUP) ->get(); - foreach ($servers as $server) { - // Just create a new audit entry for this event and update the server state - // so that power actions, file management, and backups can resume as normal. - $server->audit(AuditLog::SERVER__BACKUP_RESTORE_FAILED, function (AuditLog $audit, Server $server) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $server->getAttribute('backup_uuid')]; - $server->update(['status' => null]); - }); - } - - // Update any server marked as installing or restoring as being in a normal state - // at this point in the process. - Server::query()->where('node_id', $node->id) - ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) - ->update(['status' => null]); + $this->connection->transaction(function () use ($node, $servers) { + /** @var Server $server */ + foreach ($servers as $server) { + /** @var \Pterodactyl\Models\ActivityLog|null $activity */ + $activity = $server->activity->first(); + if (!is_null($activity)) { + if ($subject = $activity->subjects->where('subject_type', 'backup')->first()) { + // Just create a new audit entry for this event and update the server state + // so that power actions, file management, and backups can resume as normal. + Activity::event('server:backup.restore-failed') + ->subject($server, $subject->subject) + ->property('name', $subject->subject->name) // @phpstan-ignore property.notFound + ->log(); + } + } + } + + // Update any server marked as installing or restoring as being in a normal state + // at this point in the process. + Server::query()->where('node_id', $node->id) + ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) + ->update(['status' => null]); + }); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php index be67ee53a9..e97fdb871b 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -2,11 +2,13 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; +use Carbon\CarbonImmutable; use Illuminate\Http\Request; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Events\Server\Installed as ServerInstalled; use Illuminate\Contracts\Events\Dispatcher as EventDispatcher; @@ -14,38 +16,28 @@ class ServerInstallController extends Controller { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $eventDispatcher; - /** * ServerInstallController constructor. */ - public function __construct(ServerRepository $repository, EventDispatcher $eventDispatcher) + public function __construct(private ServerRepository $repository, private EventDispatcher $eventDispatcher) { - $this->repository = $repository; - $this->eventDispatcher = $eventDispatcher; } /** * Returns installation information for a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(Request $request, string $uuid) + public function index(Request $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); $egg = $server->egg; - return JsonResponse::create([ + if (! $server->node->is($request->attributes->get('node'))) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } + + return new JsonResponse([ 'container_image' => $egg->copy_script_container, 'entrypoint' => $egg->copy_script_entry, 'script' => $egg->copy_script_install, @@ -55,24 +47,40 @@ public function index(Request $request, string $uuid) /** * Updates the installation state of a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(InstallationDataRequest $request, string $uuid) + public function store(InstallationDataRequest $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); + $status = null; + + if (! $server->node->is($request->attributes->get('node'))) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } - $status = $request->boolean('successful') ? null : Server::STATUS_INSTALL_FAILED; + // Make sure the type of failure is accurate + if (!$request->boolean('successful')) { + $status = Server::STATUS_INSTALL_FAILED; + + if ($request->boolean('reinstall')) { + $status = Server::STATUS_REINSTALL_FAILED; + } + } + + // Keep the server suspended if it's already suspended if ($server->status === Server::STATUS_SUSPENDED) { $status = Server::STATUS_SUSPENDED; } - $this->repository->update($server->id, ['status' => $status], true, true); + $this->repository->update($server->id, ['status' => $status, 'installed_at' => CarbonImmutable::now()], true, true); // If the server successfully installed, fire installed event. - if ($status === null) { + // This logic allows individually disabling install and reinstall notifications separately. + $isInitialInstall = is_null($server->installed_at); + if ($isInitialInstall && config()->get('pterodactyl.email.send_install_notification', true)) { + $this->eventDispatcher->dispatch(new ServerInstalled($server)); + } elseif (!$isInitialInstall && config()->get('pterodactyl.email.send_reinstall_notification', true)) { $this->eventDispatcher->dispatch(new ServerInstalled($server)); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 6b1df3b9d6..02311beadd 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -2,8 +2,9 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; -use Carbon\CarbonImmutable; use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Webmozart\Assert\Assert; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; @@ -11,131 +12,70 @@ use Pterodactyl\Models\ServerTransfer; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Nodes\NodeJWTService; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Repositories\Wings\DaemonTransferRepository; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Services\Servers\ServerConfigurationStructureService; class ServerTransferController extends Controller { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonTransferRepository - */ - private $daemonTransferRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * ServerTransferController constructor. */ public function __construct( - ConnectionInterface $connection, - ServerRepository $repository, - DaemonServerRepository $daemonServerRepository, - DaemonTransferRepository $daemonTransferRepository, - ServerConfigurationStructureService $configurationStructureService, - NodeJWTService $jwtService + private ConnectionInterface $connection, + private ServerRepository $repository, + private DaemonServerRepository $daemonServerRepository, ) { - $this->connection = $connection; - $this->repository = $repository; - $this->daemonServerRepository = $daemonServerRepository; - $this->daemonTransferRepository = $daemonTransferRepository; - $this->configurationStructureService = $configurationStructureService; - $this->jwtService = $jwtService; } /** - * The daemon notifies us about the archive status. - * - * @return \Illuminate\Http\JsonResponse + * The daemon notifies us about a transfer failure. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Throwable */ - public function archive(Request $request, string $uuid) + public function failure(Request $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); - - // Unsuspend the server and don't continue the transfer. - if (!$request->input('successful')) { - return $this->processFailedTransfer($server->transfer); + $transfer = $server->transfer; + if (is_null($transfer)) { + throw new ConflictHttpException('Server is not being transferred.'); } - $this->connection->transaction(function () use ($server) { - // This token is used by the new node the server is being transferred to. It allows - // that node to communicate with the old node during the process to initiate the - // actual file transfer. - $token = $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setSubject($server->uuid) - ->handle($server->node, $server->uuid, 'sha256'); - - // Update the archived field on the transfer to make clients connect to the websocket - // on the new node to be able to receive transfer logs. - $server->transfer->forceFill(['archived' => true])->saveOrFail(); - - // On the daemon transfer repository, make sure to set the node after the server - // because setServer() tells the repository to use the server's node and not the one - // we want to specify. - $this->daemonTransferRepository - ->setServer($server) - ->setNode($server->transfer->newNode) - ->notify($server, $token); - }); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); - } + /* @var Node $node */ + Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class); - /** - * The daemon notifies us about a transfer failure. - * - * @return \Illuminate\Http\JsonResponse - * - * @throws \Throwable - */ - public function failure(string $uuid) - { - $server = $this->repository->getByUuid($uuid); + // Either node can tell the panel that the transfer has failed. Only the new node + // can tell the panel that it was successful. + if (! $node->is($transfer->newNode) && ! $node->is($transfer->oldNode)) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } - return $this->processFailedTransfer($server->transfer); + return $this->processFailedTransfer($transfer); } /** * The daemon notifies us about a transfer success. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function success(string $uuid): JsonResponse + public function success(Request $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); $transfer = $server->transfer; + if (is_null($transfer)) { + throw new ConflictHttpException('Server is not being transferred.'); + } + + /* @var Node $node */ + Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class); + + // Only the new node communicates a successful state to the panel, so we should + // not allow the old node to hit this endpoint. + if (! $node->is($transfer->newNode)) { + throw new HttpForbiddenException('Requesting node does not have permission to access this server.'); + } /** @var \Pterodactyl\Models\Server $server */ $server = $this->connection->transaction(function () use ($server, $transfer) { @@ -170,14 +110,12 @@ public function success(string $uuid): JsonResponse } /** - * Release all of the reserved allocations for this transfer and mark it as failed in + * Release all the reserved allocations for this transfer and mark it as failed in * the database. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - protected function processFailedTransfer(ServerTransfer $transfer) + protected function processFailedTransfer(ServerTransfer $transfer): JsonResponse { $this->connection->transaction(function () use (&$transfer) { $transfer->forceFill(['successful' => false])->saveOrFail(); diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 4a04c30890..6db0d45dfc 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -3,15 +3,18 @@ namespace Pterodactyl\Http\Controllers\Api\Remote; use Illuminate\Http\Request; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; +use phpseclib3\Crypt\PublicKeyLoader; use Pterodactyl\Http\Controllers\Controller; +use phpseclib3\Exception\NoKeyLoadedException; use Illuminate\Foundation\Auth\ThrottlesLogins; -use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\GetUserPermissionsService; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; @@ -19,91 +22,135 @@ class SftpAuthenticationController extends Controller { use ThrottlesLogins; - /** - * @var \Pterodactyl\Repositories\Eloquent\UserRepository - */ - private $userRepository; + public function __construct(protected GetUserPermissionsService $permissions) + { + } /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository + * Authenticate a set of credentials and return the associated server details + * for a SFTP connection on the daemon. This supports both public key and password + * based credentials. */ - private $serverRepository; + public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + { + $connection = $this->parseUsername($request->input('username')); + if (empty($connection['server'])) { + throw new BadRequestHttpException('No valid server identifier was included in the request.'); + } + + if ($this->hasTooManyLoginAttempts($request)) { + $seconds = $this->limiter()->availableIn($this->throttleKey($request)); + + throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in $seconds seconds."); + } + + $user = $this->getUser($request, $connection['username']); + $server = $this->getServer($request, $connection['server']); + + if ($request->input('type') !== 'public_key') { + if (!password_verify($request->input('password'), $user->password)) { + Activity::event('auth:sftp.fail')->property('method', 'password')->subject($user)->log(); + + $this->reject($request); + } + } else { + $key = null; + try { + $key = PublicKeyLoader::loadPublicKey(trim($request->input('password'))); + } catch (NoKeyLoadedException) { + // do nothing + } + + if (!$key || !$user->sshKeys()->where('fingerprint', $key->getFingerprint('sha256'))->exists()) { + // We don't log here because of the way the SFTP system works. This endpoint + // will get hit for every key the user provides, which could be 4 or 5. That is + // a lot of unnecessary log noise. + // + // For now, we'll only log failures due to a bad password as those are not likely + // to occur more than once in a session for the user, and are more likely to be of + // value to the end user. + $this->reject($request, is_null($key)); + } + } + + $this->validateSftpAccess($user, $server); + + return new JsonResponse([ + 'user' => $user->uuid, + 'server' => $server->uuid, + 'permissions' => $this->permissions->handle($server, $user), + ]); + } /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService + * Finds the server being requested and ensures that it belongs to the node this + * request stems from. */ - private $permissionsService; + protected function getServer(Request $request, string $uuid): Server + { + return Server::query() + ->where(fn ($builder) => $builder->where('uuid', $uuid)->orWhere('uuidShort', $uuid)) + ->where('node_id', $request->attributes->get('node')->id) + ->firstOr(function () use ($request) { + $this->reject($request); + }); + } /** - * SftpController constructor. + * Finds a user with the given username or increments the login attempts. */ - public function __construct( - GetUserPermissionsService $permissionsService, - UserRepository $userRepository, - ServerRepository $serverRepository - ) { - $this->userRepository = $userRepository; - $this->serverRepository = $serverRepository; - $this->permissionsService = $permissionsService; + protected function getUser(Request $request, string $username): User + { + return User::query()->where('username', $username)->firstOr(function () use ($request) { + $this->reject($request); + }); } /** - * Authenticate a set of credentials and return the associated server details - * for a SFTP connection on the daemon. + * Parses the username provided to the request. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @return array{"username": string, "server": string} */ - public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + protected function parseUsername(string $value): array { // Reverse the string to avoid issues with usernames that contain periods. - $parts = explode('.', strrev($request->input('username')), 2); + $parts = explode('.', strrev($value), 2); // Unreverse the strings after parsing them apart. - $connection = [ + return [ 'username' => strrev(array_get($parts, 1)), 'server' => strrev(array_get($parts, 0)), ]; + } - if ($this->hasTooManyLoginAttempts($request)) { - $seconds = $this->limiter()->availableIn($this->throttleKey($request)); - - throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); - } - - /** @var \Pterodactyl\Models\Node $node */ - $node = $request->attributes->get('node'); - if (empty($connection['server'])) { - throw new NotFoundHttpException(); - } - - /** @var \Pterodactyl\Models\User $user */ - $user = $this->userRepository->findFirstWhere([ - ['username', '=', $connection['username']], - ]); - - $server = $this->serverRepository->getByUuid($connection['server'] ?? ''); - if (!password_verify($request->input('password'), $user->password) || $server->node_id !== $node->id) { + /** + * Rejects the request and increments the login attempts. + */ + protected function reject(Request $request, bool $increment = true): void + { + if ($increment) { $this->incrementLoginAttempts($request); - - throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); } + throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); + } + + /** + * Validates that a user should have permission to use SFTP for the given server. + */ + protected function validateSftpAccess(User $user, Server $server): void + { if (!$user->root_admin && $server->owner_id !== $user->id) { - $permissions = $this->permissionsService->handle($server, $user); + $permissions = $this->permissions->handle($server, $user); if (!in_array(Permission::ACTION_FILE_SFTP, $permissions)) { + Activity::event('server:sftp.denied')->actor($user)->subject($server)->log(); + throw new HttpForbiddenException('You do not have permission to access SFTP for this server.'); } } $server->validateCurrentState(); - - return new JsonResponse([ - 'server' => $server->uuid, - // Deprecated, but still needed at the moment for Wings. - 'token' => '', - 'permissions' => $permissions ?? ['*'], - ]); } /** @@ -113,6 +160,6 @@ protected function throttleKey(Request $request): string { $username = explode('.', strrev($request->input('username', ''))); - return strtolower(strrev($username[0] ?? '') . '|' . $request->ip()); + return strtolower(strrev($username[0] ?? '') . '|' . $request->ip()); // @phpstan-ignore nullCoalesce.offset } } diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index e50a8050f0..11d3744c01 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -8,6 +8,8 @@ use Illuminate\Http\JsonResponse; use Illuminate\Auth\Events\Failed; use Illuminate\Container\Container; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\Auth\DirectLogin; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Auth\Authenticatable; @@ -21,24 +23,18 @@ abstract class AbstractLoginController extends Controller /** * Lockout time for failed login requests. - * - * @var int */ - protected $lockoutTime; + protected int $lockoutTime; /** * After how many attempts should logins be throttled and locked. - * - * @var int */ - protected $maxLoginAttempts; + protected int $maxLoginAttempts; /** * Where to redirect users after login / registration. - * - * @var string */ - protected $redirectTo = '/'; + protected string $redirectTo = '/'; /** * LoginController constructor. @@ -53,9 +49,11 @@ public function __construct() /** * Get the failed login response instance. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @return never-return + * + * @throws DisplayException */ - protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null) + protected function sendFailedLoginResponse(Request $request, ?Authenticatable $user = null, ?string $message = null) { $this->incrementLoginAttempts($request); $this->fireFailedLoginEvent($user, [ @@ -81,6 +79,8 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse $this->auth->guard()->login($user, true); + Event::dispatch(new DirectLogin($user, true)); + return new JsonResponse([ 'data' => [ 'complete' => true, @@ -91,12 +91,9 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse } /** - * Determine if the user is logging in using an email or username,. - * - * @param string|null $input - * @return string + * Determine if the user is logging in using an email or username. */ - protected function getField(string $input = null): string + protected function getField(?string $input = null): string { return ($input && str_contains($input, '@')) ? 'email' : 'username'; } @@ -104,8 +101,8 @@ protected function getField(string $input = null): string /** * Fire a failed login event. */ - protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) + protected function fireFailedLoginEvent(?Authenticatable $user = null, array $credentials = []) { - event(new Failed('auth', $user, $credentials)); + Event::dispatch(new Failed('auth', $user, $credentials)); } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 7b409e519d..d67b0de516 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -15,9 +15,6 @@ class ForgotPasswordController extends Controller /** * Get the response for a failed password reset link. - * - * @param \Illuminate\Http\Request - * @param string $response */ protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse { diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index fdb5b7e138..ed212e4556 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -2,13 +2,16 @@ namespace Pterodactyl\Http\Controllers\Auth; -use Carbon\CarbonInterface; +use Carbon\Carbon; use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; use PragmaRX\Google2FA\Google2FA; +use Illuminate\Support\Facades\Event; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Pterodactyl\Events\Auth\ProvidedAuthenticationToken; use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest; use Illuminate\Contracts\Validation\Factory as ValidationFactory; @@ -16,22 +19,15 @@ class LoginCheckpointController extends AbstractLoginController { private const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again.'; - private ValidationFactory $validation; - - private Google2FA $google2FA; - - private Encrypter $encrypter; - /** * LoginCheckpointController constructor. */ - public function __construct(Encrypter $encrypter, Google2FA $google2FA, ValidationFactory $validation) - { + public function __construct( + private Encrypter $encrypter, + private Google2FA $google2FA, + private ValidationFactory $validation, + ) { parent::__construct(); - - $this->google2FA = $google2FA; - $this->encrypter = $encrypter; - $this->validation = $validation; } /** @@ -39,8 +35,6 @@ public function __construct(Encrypter $encrypter, Google2FA $google2FA, Validati * token. Once a user has reached this stage it is assumed that they have already * provided a valid username and password. * - * @return \Illuminate\Http\JsonResponse|void - * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException @@ -63,21 +57,36 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse } try { - /** @var \Pterodactyl\Models\User $user */ $user = User::query()->findOrFail($details['user_id']); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { $this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE); } // Recovery tokens go through a slightly different pathway for usage. if (!is_null($recoveryToken = $request->input('recovery_token'))) { if ($this->isValidRecoveryToken($user, $recoveryToken)) { + Event::dispatch(new ProvidedAuthenticationToken($user, true)); + return $this->sendLoginResponse($user, $request); } } else { $decrypted = $this->encrypter->decrypt($user->totp_secret); + $oldTimestamp = $user->totp_authenticated_at + ? (int) floor($user->totp_authenticated_at->unix() / $this->google2FA->getKeyRegeneration()) + : null; + + $verified = $this->google2FA->verifyKeyNewer( + $decrypted, + $request->input('authentication_code') ?? '', + $oldTimestamp, + config('pterodactyl.auth.2fa.window') ?? 1, + ); + + if ($verified !== false) { + $user->update(['totp_authenticated_at' => Carbon::now()]); + + Event::dispatch(new ProvidedAuthenticationToken($user)); - if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { return $this->sendLoginResponse($user, $request); } } @@ -89,11 +98,9 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse * Determines if a given recovery token is valid for the user account. If we find a matching token * it will be deleted from the database. * - * @return bool - * * @throws \Exception */ - protected function isValidRecoveryToken(User $user, string $value) + protected function isValidRecoveryToken(User $user, string $value): bool { foreach ($user->recoveryTokens as $token) { if (password_verify($value, $token->token)) { @@ -110,13 +117,10 @@ protected function isValidRecoveryToken(User $user, string $value) * Determines if the data provided from the session is valid or not. This * will return false if the data is invalid, or if more time has passed than * was configured when the session was written. - * - * @param array $data - * @return bool */ - protected function hasValidSessionData(array $data): bool + protected function hasValidSessionData(?array $data): bool { - $validator = $this->validation->make($data, [ + $validator = $this->validation->make($data ?? [], [ 'user_id' => 'required|integer|min:1', 'token_value' => 'required|string', 'expires_at' => 'required', diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3c477c5568..1ca321c1e5 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -7,39 +7,25 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Illuminate\Contracts\View\View; -use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Database\Eloquent\ModelNotFoundException; class LoginController extends AbstractLoginController { - private ViewFactory $view; - - /** - * LoginController constructor. - */ - public function __construct(ViewFactory $view) - { - parent::__construct(); - - $this->view = $view; - } - /** * Handle all incoming requests for the authentication routes and render the - * base authentication view component. Vuejs will take over at this point and - * turn the login area into a SPA. + * base authentication view component. React will take over at this point and + * turn the login area into an SPA. */ public function index(): View { - return $this->view->make('templates/auth.core'); + return view('templates/auth.core'); } /** * Handle a login request to the application. * - * @return \Illuminate\Http\JsonResponse|void - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException */ @@ -53,39 +39,37 @@ public function login(Request $request): JsonResponse try { $username = $request->input('user'); - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::query()->where($this->getField($username), $username)->firstOrFail(); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { $this->sendFailedLoginResponse($request); } // Ensure that the account is using a valid username and password before trying to // continue. Previously this was handled in the 2FA checkpoint, however that has // a flaw in which you can discover if an account exists simply by seeing if you - // can proceede to the next step in the login process. + // can proceed to the next step in the login process. if (!password_verify($request->input('password'), $user->password)) { $this->sendFailedLoginResponse($request, $user); } - if ($user->use_totp) { - $token = Str::random(64); - - $request->session()->put('auth_confirmation_token', [ - 'user_id' => $user->id, - 'token_value' => $token, - 'expires_at' => CarbonImmutable::now()->addMinutes(5), - ]); - - return new JsonResponse([ - 'data' => [ - 'complete' => false, - 'confirmation_token' => $token, - ], - ]); + if (!$user->use_totp) { + return $this->sendLoginResponse($user, $request); } - $this->auth->guard()->login($user, true); + Activity::event('auth:checkpoint')->withRequestMetadata()->subject($user)->log(); + + $request->session()->put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => $token = Str::random(64), + 'expires_at' => CarbonImmutable::now()->addMinutes(5), + ]); - return $this->sendLoginResponse($user, $request); + return new JsonResponse([ + 'data' => [ + 'complete' => false, + 'confirmation_token' => $token, + ], + ]); } } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 1107510a84..343f33bcac 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Password; use Illuminate\Auth\Events\PasswordReset; use Illuminate\Contracts\Events\Dispatcher; +use Pterodactyl\Events\User\PasswordChanged; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; @@ -20,51 +21,31 @@ class ResetPasswordController extends Controller /** * The URL to redirect users to after password reset. - * - * @var string */ - public $redirectTo = '/'; + public string $redirectTo = '/'; - /** - * @var bool - */ - protected $hasTwoFactor = false; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $dispatcher; - - /** - * @var \Illuminate\Contracts\Hashing\Hasher - */ - private $hasher; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $userRepository; + protected bool $hasTwoFactor = false; /** * ResetPasswordController constructor. */ - public function __construct(Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository) - { - $this->dispatcher = $dispatcher; - $this->hasher = $hasher; - $this->userRepository = $userRepository; + public function __construct( + private Dispatcher $dispatcher, + private Hasher $hasher, + private UserRepositoryInterface $userRepository, + ) { } /** * Reset the given user's password. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ public function __invoke(ResetPasswordRequest $request): JsonResponse { // Here we will attempt to reset the user's password. If it is successful we // will update the password on an actual user model and persist it to the - // database. Otherwise we will parse the error and return the response. + // database. Otherwise, we will parse the error and return the response. $response = $this->broker()->reset( $this->credentials($request), function ($user, $password) { @@ -87,7 +68,7 @@ function ($user, $password) { * account do not automatically log them in. In those cases, send the user back to the login * form with a note telling them their password was changed and to log back in. * - * @param \Illuminate\Contracts\Auth\CanResetPassword|\Pterodactyl\Models\User $user + * @param \Illuminate\Contracts\Auth\CanResetPassword&\Pterodactyl\Models\User $user * @param string $password * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -101,6 +82,7 @@ protected function resetPassword($user, $password) ]); $this->dispatcher->dispatch(new PasswordReset($user)); + PasswordChanged::dispatch($user); // If the user is not using 2FA log them in, otherwise skip this step and force a // fresh login where they'll be prompted to enter a token. diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 058280be69..37d33ab95b 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -2,30 +2,26 @@ namespace Pterodactyl\Http\Controllers\Base; +use Illuminate\View\View; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class IndexController extends Controller { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - /** * IndexController constructor. */ - public function __construct(ServerRepositoryInterface $repository) - { - $this->repository = $repository; + public function __construct( + protected ServerRepositoryInterface $repository, + protected ViewFactory $view, + ) { } /** * Returns listing of user's servers. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { return view('templates/base.core'); } diff --git a/app/Http/Controllers/Base/LocaleController.php b/app/Http/Controllers/Base/LocaleController.php index 3bb6fbfba2..16a3100929 100644 --- a/app/Http/Controllers/Base/LocaleController.php +++ b/app/Http/Controllers/Base/LocaleController.php @@ -2,37 +2,63 @@ namespace Pterodactyl\Http\Controllers\Base; -use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use Illuminate\Translation\Translator; +use Illuminate\Contracts\Translation\Loader; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Http\Requests\Base\LocaleRequest; class LocaleController extends Controller { - /** - * @var \Illuminate\Translation\Translator - */ - private $translator; + protected Loader $loader; - /** - * LocaleController constructor. - */ public function __construct(Translator $translator) { - $this->translator = $translator; + $this->loader = $translator->getLoader(); } /** * Returns translation data given a specific locale and namespace. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(Request $request, string $locale, string $namespace) + public function __invoke(LocaleRequest $request): JsonResponse { - $data = $this->translator->getLoader()->load($locale, str_replace('.', '/', $namespace)); + $locale = $request->input('locale'); + $namespace = $request->input('namespace'); + $response[$locale][$namespace] = $this->i18n($this->loader->load($locale, $namespace)); - return JsonResponse::create($data, 200, [ - 'E-Tag' => md5(json_encode($data)), + return new JsonResponse($response, 200, [ + // Cache this in the browser for an hour, and allow the browser to use a stale + // cache for up to a day after it was created while it fetches an updated set + // of translation keys. + 'Cache-Control' => 'public, max-age=3600, stale-while-revalidate=86400', + 'ETag' => md5(json_encode($response, JSON_THROW_ON_ERROR)), ]); } + + /** + * Convert standard Laravel translation keys that look like ":foo" + * into key structures that are supported by the front-end i18n + * library, like "{{foo}}". + */ + protected function i18n(array $data): array + { + foreach ($data as $key => $value) { + if (is_array($value)) { + $data[$key] = $this->i18n($value); + } else { + // Find a Laravel style translation replacement in the string and replace it with + // one that the front-end is able to use. This won't always be present, especially + // for complex strings or things where we'd never have a backend component anyways. + // + // For example: + // "Hello :name, the :notifications.0.title notification needs :count actions :foo.0.bar." + // + // Becomes: + // "Hello {{name}}, the {{notifications.0.title}} notification needs {{count}} actions {{foo.0.bar}}." + $data[$key] = preg_replace('/:([\w.-]+\w)([^\w:]?|$)/m', '{{$1}}$2', $value); + } + } + + return $data; + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3fac902c1b..dc718cd4cf 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,97 +2,86 @@ namespace Pterodactyl\Http; -use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Http\Middleware\HandleCors; use Illuminate\Auth\Middleware\Authenticate; +use Illuminate\Http\Middleware\TrustProxies; use Pterodactyl\Http\Middleware\TrimStrings; -use Pterodactyl\Http\Middleware\TrustProxies; use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; use Pterodactyl\Http\Middleware\VerifyReCaptcha; -use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; +use Pterodactyl\Http\Middleware\SetSecurityHeaders; use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Pterodactyl\Http\Middleware\Api\AuthenticateKey; use Illuminate\Routing\Middleware\SubstituteBindings; +use Pterodactyl\Http\Middleware\Activity\TrackAPIKey; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; use Pterodactyl\Http\Middleware\MaintenanceMiddleware; +use Pterodactyl\Http\Middleware\EnsureStatefulRequests; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; -use Pterodactyl\Http\Middleware\Api\HandleStatelessRequest; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; +use Pterodactyl\Http\Middleware\Api\Client\RequireClientApiKey; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; -use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; +use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; +use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. - * - * @var array */ protected $middleware = [ - CheckForMaintenanceMode::class, - EncryptCookies::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, ValidatePostSize::class, TrimStrings::class, ConvertEmptyStringsToNull::class, - TrustProxies::class, + SetSecurityHeaders::class, + ]; + + protected $middlewarePriority = [ + SubstituteClientBindings::class, ]; /** * The application's route middleware groups. - * - * @var array */ protected $middlewareGroups = [ 'web' => [ + EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, - AuthenticateSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, SubstituteBindings::class, LanguageMiddleware::class, - RequireTwoFactorAuthentication::class, ], 'api' => [ - HandleStatelessRequest::class, + EnsureStatefulRequests::class, + 'auth:sanctum', IsValidJson::class, - StartSession::class, - AuthenticateSession::class, - ApiSubstituteBindings::class, - 'api..key:' . ApiKey::TYPE_APPLICATION, - AuthenticateApplicationUser::class, - VerifyCsrfToken::class, + TrackAPIKey::class, + RequireTwoFactorAuthentication::class, AuthenticateIPAccess::class, ], + 'application-api' => [ + SubstituteBindings::class, + AuthenticateApplicationUser::class, + ], 'client-api' => [ - HandleStatelessRequest::class, - IsValidJson::class, - StartSession::class, - AuthenticateSession::class, - SubstituteClientApiBindings::class, - 'api..key:' . ApiKey::TYPE_ACCOUNT, - AuthenticateIPAccess::class, - VerifyCsrfToken::class, - // This is perhaps a little backwards with the Client API, but logically you'd be unable - // to create/get an API key without first enabling 2FA on the account, so I suppose in the - // end it makes sense. - // - // You just wouldn't be authenticating with the API by providing a 2FA token. - RequireTwoFactorAuthentication::class, + SubstituteClientBindings::class, + RequireClientApiKey::class, ], 'daemon' => [ SubstituteBindings::class, @@ -102,21 +91,17 @@ class Kernel extends HttpKernel /** * The application's route middleware. - * - * @var array */ - protected $routeMiddleware = [ + protected $middlewareAliases = [ 'auth' => Authenticate::class, 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, 'guest' => RedirectIfAuthenticated::class, - 'admin' => AdminAuthenticate::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, 'can' => Authorize::class, 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // API Specific Middleware - 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Http/Middleware/Activity/AccountSubject.php b/app/Http/Middleware/Activity/AccountSubject.php new file mode 100644 index 0000000000..fba1ea087d --- /dev/null +++ b/app/Http/Middleware/Activity/AccountSubject.php @@ -0,0 +1,21 @@ +user()); + LogTarget::setSubject($request->user()); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Activity/ServerSubject.php b/app/Http/Middleware/Activity/ServerSubject.php new file mode 100644 index 0000000000..c4dd160c8e --- /dev/null +++ b/app/Http/Middleware/Activity/ServerSubject.php @@ -0,0 +1,29 @@ +route()->parameter('server'); + if ($server instanceof Server) { + LogTarget::setActor($request->user()); + LogTarget::setSubject($server); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Activity/TrackAPIKey.php b/app/Http/Middleware/Activity/TrackAPIKey.php new file mode 100644 index 0000000000..5f45d0cbcf --- /dev/null +++ b/app/Http/Middleware/Activity/TrackAPIKey.php @@ -0,0 +1,27 @@ +user()) { + $token = $request->user()->currentAccessToken(); + + LogTarget::setApiKeyId($token instanceof ApiKey ? $token->id : null); // @phpstan-ignore instanceof.alwaysTrue + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Admin/Servers/ServerInstalled.php b/app/Http/Middleware/Admin/Servers/ServerInstalled.php index baf88af2de..d3fa75951e 100644 --- a/app/Http/Middleware/Admin/Servers/ServerInstalled.php +++ b/app/Http/Middleware/Admin/Servers/ServerInstalled.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Middleware\Admin\Servers; -use Closure; use Illuminate\Http\Request; use Illuminate\Http\Response; use Pterodactyl\Models\Server; @@ -13,12 +12,10 @@ class ServerInstalled { /** * Checks that the server is installed before allowing access through the route. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { - /** @var \Pterodactyl\Models\Server|null $server */ + /** @var Server|null $server */ $server = $request->route()->parameter('server'); if (!$server instanceof Server) { diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index 72a7c86294..2e61f64296 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -1,15 +1,7 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Middleware; -use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -18,11 +10,9 @@ class AdminAuthenticate /** * Handle an incoming request. * - * @return mixed - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @throws AccessDeniedHttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { if (!$request->user() || !$request->user()->root_admin) { throw new AccessDeniedHttpException(); diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php deleted file mode 100644 index 7ade7452a0..0000000000 --- a/app/Http/Middleware/Api/ApiSubstituteBindings.php +++ /dev/null @@ -1,87 +0,0 @@ - Allocation::class, - 'database' => Database::class, - 'egg' => Egg::class, - 'location' => Location::class, - 'nest' => Nest::class, - 'node' => Node::class, - 'server' => Server::class, - 'user' => User::class, - ]; - - /** - * @var \Illuminate\Routing\Router - */ - protected $router; - - /** - * Perform substitution of route parameters without triggering - * a 404 error if a model is not found. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - */ - public function handle($request, Closure $next) - { - $route = $request->route(); - - foreach (self::$mappings as $key => $model) { - if (!is_null($this->router->getBindingCallback($key))) { - continue; - } - - $this->router->model($key, $model, function () use ($request) { - $request->attributes->set('is_missing_model', true); - }); - } - - $this->router->substituteBindings($route); - - // Attempt to resolve bindings for this route. If one of the models - // cannot be resolved do not immediately return a 404 error. Set a request - // attribute that can be checked in the base API request class to only - // trigger a 404 after validating that the API key making the request is valid - // and even has permission to access the requested resource. - try { - $this->router->substituteImplicitBindings($route); - } catch (ModelNotFoundException $exception) { - $request->attributes->set('is_missing_model', true); - } - - return $next($request); - } - - /** - * Return the registered mappings. - * - * @return array - */ - public static function getMappings() - { - return self::$mappings; - } -} diff --git a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php index bf9a64606a..e6f83b433a 100644 --- a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php +++ b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Middleware\Api\Application; -use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -11,12 +10,12 @@ class AuthenticateApplicationUser /** * Authenticate that the currently authenticated user is an administrator * and should be allowed to proceed through the application API. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { - if (is_null($request->user()) || !$request->user()->root_admin) { + /** @var \Pterodactyl\Models\User|null $user */ + $user = $request->user(); + if (!$user || !$user->root_admin) { throw new AccessDeniedHttpException('This account does not have permission to access the API.'); } diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php index 2af34cfd9f..e2332de3ad 100644 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ b/app/Http/Middleware/Api/AuthenticateIPAccess.php @@ -2,10 +2,11 @@ namespace Pterodactyl\Http\Middleware\Api; -use Closure; use IPTools\IP; use IPTools\Range; use Illuminate\Http\Request; +use Pterodactyl\Facades\Activity; +use Laravel\Sanctum\TransientToken; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateIPAccess @@ -13,26 +14,35 @@ class AuthenticateIPAccess /** * Determine if a request IP has permission to access the API. * - * @return mixed - * * @throws \Exception - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @throws AccessDeniedHttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { - $model = $request->attributes->get('api_key'); + /** @var TransientToken|\Pterodactyl\Models\ApiKey $token */ + $token = $request->user()->currentAccessToken(); - if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { + // If this is a stateful request just push the request through to the next + // middleware in the stack, there is nothing we need to explicitly check. If + // this is a valid API Key, but there is no allowed IP restriction, also pass + // the request through. + if ($token instanceof TransientToken || empty($token->allowed_ips)) { return $next($request); } $find = new IP($request->ip()); - foreach ($model->allowed_ips as $ip) { + foreach ($token->allowed_ips as $ip) { if (Range::parse($ip)->contains($find)) { return $next($request); } } + Activity::event('auth:ip-blocked') + ->actor($request->user()) + ->subject($request->user(), $token) + ->property('identifier', $token->identifier) + ->log(); + throw new AccessDeniedHttpException('This IP address (' . $request->ip() . ') does not have permission to access the API using these credentials.'); } } diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php deleted file mode 100644 index 857bfab29e..0000000000 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ /dev/null @@ -1,109 +0,0 @@ -auth = $auth; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Handle an API request by verifying that the provided API key is in a valid - * format and exists in the database. If there is currently a user in the session - * do not even bother to look at the token (they provided a cookie for this to - * be the case). - * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next, int $keyType) - { - if (is_null($request->bearerToken()) && is_null($request->user())) { - throw new HttpException(401, 'A bearer token or valid user session cookie must be provided to access this endpoint.', null, ['WWW-Authenticate' => 'Bearer']); - } - - // This is a request coming through using cookies, we have an authenticated user - // not using an API key. Make some fake API key models and continue on through - // the process. - if ($request->user() instanceof User) { - $model = (new ApiKey())->forceFill([ - 'user_id' => $request->user()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); - } else { - $model = $this->authenticateApiKey($request->bearerToken(), $keyType); - - $this->auth->guard()->onceUsingId($model->user_id); - } - - $request->attributes->set('api_key', $model); - - return $next($request); - } - - /** - * Authenticate an API key. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - protected function authenticateApiKey(string $key, int $keyType): ApiKey - { - $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($key, ApiKey::IDENTIFIER_LENGTH); - - try { - $model = $this->repository->findFirstWhere([ - ['identifier', '=', $identifier], - ['key_type', '=', $keyType], - ]); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException(); - } - - if (!hash_equals($this->encrypter->decrypt($model->token), $token)) { - throw new AccessDeniedHttpException(); - } - - $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]); - - return $model; - } -} diff --git a/app/Http/Middleware/Api/Client/RequireClientApiKey.php b/app/Http/Middleware/Api/Client/RequireClientApiKey.php new file mode 100644 index 0000000000..6c815fc35a --- /dev/null +++ b/app/Http/Middleware/Api/Client/RequireClientApiKey.php @@ -0,0 +1,25 @@ +user()->currentAccessToken(); + + if ($token instanceof ApiKey && $token->key_type === ApiKey::TYPE_APPLICATION) { // @phpstan-ignore instanceof.alwaysTrue + throw new AccessDeniedHttpException('You are attempting to use an application API key on an endpoint that requires a client API key.'); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 0634e25858..f60b696c17 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -2,43 +2,31 @@ namespace Pterodactyl\Http\Middleware\Api\Client\Server; -use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; class AuthenticateServerAccess { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * Routes that this middleware should not apply to if the user is an admin. - * - * @var string[] */ - protected $except = [ + protected array $except = [ 'api:client:server.ws', ]; /** * AuthenticateServerAccess constructor. */ - public function __construct(ServerRepositoryInterface $repository) + public function __construct() { - $this->repository = $repository; } /** * Authenticate that this server exists and is not suspended or marked as installing. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { /** @var \Pterodactyl\Models\User $user */ $user = $request->user(); @@ -64,7 +52,7 @@ public function handle(Request $request, Closure $next) // Still allow users to get information about their server if it is installing or // being transferred. if (!$request->routeIs('api:client:server.view')) { - if ($server->isSuspended() && !$request->routeIs('api:client:server.resources')) { + if (($server->isSuspended() || $server->node->isUnderMaintenance()) && !$request->routeIs('api:client:server.resources')) { throw $exception; } if (!$user->root_admin || !$request->routeIs($this->except)) { diff --git a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php index d8c4731d3d..43a48a4085 100644 --- a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php +++ b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php @@ -2,11 +2,9 @@ namespace Pterodactyl\Http\Middleware\Api\Client\Server; -use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Task; use Pterodactyl\Models\User; -use InvalidArgumentException; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; @@ -25,17 +23,15 @@ class ResourceBelongsToServer * This is critical to ensuring that all subsequent logic is using exactly the * server that is expected, and that we're not accessing a resource completely * unrelated to the server provided in the request. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { $params = $request->route()->parameters(); - if (is_null($params) || !$params['server'] instanceof Server) { - throw new InvalidArgumentException('This middleware cannot be used in a context that is missing a server in the parameters.'); + if (! isset($params['server']) || !$params['server'] instanceof Server) { + throw new \InvalidArgumentException('This middleware cannot be used in a context that is missing a server in the parameters.'); } - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ $server = $request->route()->parameter('server'); $exception = new NotFoundHttpException('The requested resource was not found for this server.'); foreach ($params as $key => $model) { @@ -59,8 +55,8 @@ public function handle(Request $request, Closure $next) throw $exception; } break; - // Regular users are a special case here as we need to make sure they're - // currently assigned as a subuser on the server. + // Regular users are a special case here as we need to make sure they're + // currently assigned as a subuser on the server. case User::class: $subuser = $server->subusers()->where('user_id', $model->id)->first(); if (is_null($subuser)) { @@ -70,18 +66,18 @@ public function handle(Request $request, Closure $next) // in the underlying logic. $request->attributes->set('subuser', $subuser); break; - // Tasks are special since they're (currently) the only item in the API - // that requires something in addition to the server in order to be accessed. + // Tasks are special since they're (currently) the only item in the API + // that requires something in addition to the server in order to be accessed. case Task::class: $schedule = $request->route()->parameter('schedule'); - if ($model->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { + if (!$schedule instanceof Schedule || $model->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { throw $exception; } break; default: // Don't return a 404 here since we want to make sure no one relies // on this middleware in a context in which it will not work. Fail safe. - throw new InvalidArgumentException('There is no handler configured for a resource of this type: ' . get_class($model)); + throw new \InvalidArgumentException('There is no handler configured for a resource of this type: ' . get_class($model)); } } diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php deleted file mode 100644 index 31590f28a3..0000000000 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ /dev/null @@ -1,62 +0,0 @@ -router->bind('server', function ($value) use ($request) { - try { - $column = 'uuidShort'; - if (preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $value)) { - $column = 'uuid'; - } - - return Container::getInstance()->make(ServerRepositoryInterface::class)->findFirstWhere([ - [$column, '=', $value], - ]); - } catch (RecordNotFoundException $ex) { - $request->attributes->set('is_missing_model', true); - - return null; - } - }); - - $this->router->bind('database', function ($value) { - $id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); - - return Database::query()->where('id', $id)->firstOrFail(); - }); - - $this->router->bind('backup', function ($value) { - return Backup::query()->where('uuid', $value)->firstOrFail(); - }); - - $this->router->bind('user', function ($value) { - return User::query()->where('uuid', $value)->firstOrFail(); - }); - - return parent::handle($request, $next); - } -} diff --git a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php new file mode 100644 index 0000000000..3ae2dd87ed --- /dev/null +++ b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php @@ -0,0 +1,39 @@ +router->bind('server', function ($value) { + return Server::query() + ->when( + str_starts_with($value, 'serv_'), + fn ($builder) => $builder->whereIdentifier($value), + fn ($builder) => $builder->where(strlen($value) === 8 ? 'uuidShort' : 'uuid', $value) + ) + ->firstOrFail(); + }); + + $this->router->bind('user', function ($value, $route) { + /** @var \Pterodactyl\Models\Subuser $match */ + $match = $route->parameter('server') + ->subusers() + ->whereRelation('user', 'uuid', '=', $value) + ->firstOrFail(); + + return $match->user; + }); + + return parent::handle($request, $next); + } +} diff --git a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php index f79d598ebc..d782ed8cf3 100644 --- a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon; -use Closure; use Illuminate\Http\Request; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -13,42 +12,26 @@ class DaemonAuthenticate { - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * Daemon routes that this middleware should be skipped on. - * - * @var array */ - protected $except = [ + protected array $except = [ 'daemon.configuration', ]; /** * DaemonAuthenticate constructor. */ - public function __construct(Encrypter $encrypter, NodeRepository $repository) + public function __construct(private Encrypter $encrypter, private NodeRepository $repository) { - $this->repository = $repository; - $this->encrypter = $encrypter; } /** * Check if a request from the daemon can be properly attributed back to a single node instance. * - * @return mixed - * - * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * @throws HttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { if (in_array($request->route()->getName(), $this->except)) { return $next($request); diff --git a/app/Http/Middleware/Api/HandleStatelessRequest.php b/app/Http/Middleware/Api/HandleStatelessRequest.php deleted file mode 100644 index ab697d687b..0000000000 --- a/app/Http/Middleware/Api/HandleStatelessRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -bearerToken()) && $request->isJson()) { - $request->session()->getHandler()->destroy( - $request->session()->getId() - ); - - $response->headers->remove('Set-Cookie'); - } - - return $response; - } -} diff --git a/app/Http/Middleware/Api/IsValidJson.php b/app/Http/Middleware/Api/IsValidJson.php index c3c8d6c850..95101a1b29 100644 --- a/app/Http/Middleware/Api/IsValidJson.php +++ b/app/Http/Middleware/Api/IsValidJson.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Middleware\Api; -use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -12,16 +11,14 @@ class IsValidJson * Throw an exception if the request should be valid JSON data but there is an error while * parsing the data. This avoids confusing validation errors where every field is flagged and * it is not immediately clear that there is an issue with the JSON being passed. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { if ($request->isJson() && !empty($request->getContent())) { - json_decode($request->getContent(), true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new BadRequestHttpException(sprintf('The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"', json_last_error(), json_last_error_msg())); + try { + json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $exception) { + throw new BadRequestHttpException('The JSON data passed in the request appears to be malformed: ' . $exception->getMessage()); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php deleted file mode 100644 index f95180b20c..0000000000 --- a/app/Http/Middleware/Authenticate.php +++ /dev/null @@ -1,26 +0,0 @@ -user()) { - throw new AuthenticationException(); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 9c0cadd86d..1ac425a33d 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -8,8 +8,6 @@ class EncryptCookies extends BaseEncrypter { /** * The names of the cookies that should not be encrypted. - * - * @var array */ protected $except = []; } diff --git a/app/Http/Middleware/EnsureStatefulRequests.php b/app/Http/Middleware/EnsureStatefulRequests.php new file mode 100644 index 0000000000..db6e19ae91 --- /dev/null +++ b/app/Http/Middleware/EnsureStatefulRequests.php @@ -0,0 +1,26 @@ +hasCookie(config('session.cookie')); + } +} diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 914d4395f8..e98ad28638 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -2,31 +2,22 @@ namespace Pterodactyl\Http\Middleware; -use Closure; use Illuminate\Http\Request; use Illuminate\Foundation\Application; class LanguageMiddleware { - /** - * @var \Illuminate\Foundation\Application - */ - private $app; - /** * LanguageMiddleware constructor. */ - public function __construct(Application $app) + public function __construct(private Application $app) { - $this->app = $app; } /** * Handle an incoming request and set the user's preferred language. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { $this->app->setLocale($request->user()->language ?? config('app.locale', 'en')); diff --git a/app/Http/Middleware/MaintenanceMiddleware.php b/app/Http/Middleware/MaintenanceMiddleware.php index 5826261622..61247b4e08 100644 --- a/app/Http/Middleware/MaintenanceMiddleware.php +++ b/app/Http/Middleware/MaintenanceMiddleware.php @@ -2,32 +2,22 @@ namespace Pterodactyl\Http\Middleware; -use Closure; +use Illuminate\Http\Request; use Illuminate\Contracts\Routing\ResponseFactory; class MaintenanceMiddleware { - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory - */ - private $response; - /** * MaintenanceMiddleware constructor. */ - public function __construct(ResponseFactory $response) + public function __construct(private ResponseFactory $response) { - $this->response = $response; } /** * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { /** @var \Pterodactyl\Models\Server $server */ $server = $request->attributes->get('server'); diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 6c4019af46..2889506c96 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -2,31 +2,22 @@ namespace Pterodactyl\Http\Middleware; -use Closure; use Illuminate\Http\Request; use Illuminate\Auth\AuthManager; class RedirectIfAuthenticated { - /** - * @var \Illuminate\Auth\AuthManager - */ - private $authManager; - /** * RedirectIfAuthenticated constructor. */ - public function __construct(AuthManager $authManager) + public function __construct(private AuthManager $authManager) { - $this->authManager = $authManager; } /** * Handle an incoming request. - * - * @return mixed */ - public function handle(Request $request, Closure $next, string $guard = null) + public function handle(Request $request, \Closure $next, ?string $guard = null): mixed { if ($this->authManager->guard($guard)->check()) { return redirect()->route('index'); diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index 6691179a34..d609bfa602 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Middleware; -use Closure; use Illuminate\Support\Str; use Illuminate\Http\Request; use Prologue\Alerts\AlertsMessageBag; @@ -15,23 +14,15 @@ class RequireTwoFactorAuthentication public const LEVEL_ALL = 2; /** - * @var \Prologue\Alerts\AlertsMessageBag + * The route to redirect a user to enable 2FA. */ - private $alert; - - /** - * The route to redirect a user to to enable 2FA. - * - * @var string - */ - protected $redirectRoute = '/account'; + protected string $redirectRoute = '/account'; /** * RequireTwoFactorAuthentication constructor. */ - public function __construct(AlertsMessageBag $alert) + public function __construct(private AlertsMessageBag $alert) { - $this->alert = $alert; } /** @@ -40,13 +31,10 @@ public function __construct(AlertsMessageBag $alert) * order to perform actions. If so, we check the level at which it is required (all users * or just admins) and then check if the user has enabled it for their account. * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException + * @throws TwoFactorAuthRequiredException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { - /** @var \Pterodactyl\Models\User $user */ $user = $request->user(); $uri = rtrim($request->getRequestUri(), '/') . '/'; $current = $request->route()->getName(); diff --git a/app/Http/Middleware/SetSecurityHeaders.php b/app/Http/Middleware/SetSecurityHeaders.php new file mode 100644 index 0000000000..a1d2518d73 --- /dev/null +++ b/app/Http/Middleware/SetSecurityHeaders.php @@ -0,0 +1,43 @@ + 'DENY', + 'X-Content-Type-Options' => 'nosniff', + 'X-XSS-Protection' => '1; mode=block', + 'Referrer-Policy' => 'no-referrer-when-downgrade', + ]; + + /** + * Enforces some basic security headers on all responses returned by the software. + * If a header has already been set in another location within the code it will be + * skipped over here. + * + * @param (\Closure(mixed): \Illuminate\Http\Response) $next + */ + public function handle(Request $request, \Closure $next): mixed + { + $response = $next($request); + + foreach (static::$headers as $key => $value) { + if (! $response->headers->has($key)) { + $response->headers->set($key, $value); + } + } + + return $response; + } +} diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index 04f434b982..af5382e8c9 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -8,8 +8,6 @@ class TrimStrings extends BaseTrimmer { /** * The names of the attributes that should not be trimmed. - * - * @var array */ protected $except = [ 'password', diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php deleted file mode 100644 index 3dff63eeb8..0000000000 --- a/app/Http/Middleware/TrustProxies.php +++ /dev/null @@ -1,23 +0,0 @@ -attributes->get('api_key'); - - if ($key instanceof ApiKey && $key->exists) { - return $next($request); - } - - return parent::handle($request, $next); - } } diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index c0841f2c98..ef251c3333 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Middleware; -use Closure; -use stdClass; use GuzzleHttp\Client; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -14,33 +12,17 @@ class VerifyReCaptcha { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $dispatcher; - /** * VerifyReCaptcha constructor. */ - public function __construct(Dispatcher $dispatcher, Repository $config) + public function __construct(private Dispatcher $dispatcher, private Repository $config) { - $this->config = $config; - $this->dispatcher = $dispatcher; } /** * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\RedirectResponse|mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, \Closure $next): mixed { if (!$this->config->get('recaptcha.enabled')) { return $next($request); @@ -77,7 +59,7 @@ public function handle($request, Closure $next) /** * Determine if the response from the recaptcha servers was valid. */ - private function isResponseVerified(stdClass $result, Request $request): bool + private function isResponseVerified(\stdClass $result, Request $request): bool { if (!$this->config->get('recaptcha.verify_domain')) { return false; diff --git a/app/Http/Requests/Admin/AdminFormRequest.php b/app/Http/Requests/Admin/AdminFormRequest.php index e3107e26da..59ba0db64d 100644 --- a/app/Http/Requests/Admin/AdminFormRequest.php +++ b/app/Http/Requests/Admin/AdminFormRequest.php @@ -8,18 +8,14 @@ abstract class AdminFormRequest extends FormRequest { /** * The rules to apply to the incoming form request. - * - * @return array */ - abstract public function rules(); + abstract public function rules(): array; /** * Determine if the user is an admin and has permission to access this * form controller in the first place. - * - * @return bool */ - public function authorize() + public function authorize(): bool { if (is_null($this->user())) { return false; @@ -31,10 +27,8 @@ public function authorize() /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize(array $only = null) + public function normalize(?array $only = null): array { return $this->only($only ?? array_keys($this->rules())); } diff --git a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php index 40aa732f2c..f451239e67 100644 --- a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php +++ b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php @@ -9,12 +9,10 @@ class StoreApplicationApiKeyRequest extends AdminFormRequest { /** - * @return array - * * @throws \ReflectionException * @throws \ReflectionException */ - public function rules() + public function rules(): array { $modelRules = ApiKey::getRules(); @@ -23,10 +21,7 @@ public function rules() })->merge(['memo' => $modelRules['memo']])->toArray(); } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'memo' => 'Description', diff --git a/app/Http/Requests/Admin/BaseFormRequest.php b/app/Http/Requests/Admin/BaseFormRequest.php index dff6b9fb2f..cd2c78e4dc 100644 --- a/app/Http/Requests/Admin/BaseFormRequest.php +++ b/app/Http/Requests/Admin/BaseFormRequest.php @@ -1,17 +1,10 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin; class BaseFormRequest extends AdminFormRequest { - public function rules() + public function rules(): array { return [ 'company' => 'required|between:1,256', diff --git a/app/Http/Requests/Admin/DatabaseHostFormRequest.php b/app/Http/Requests/Admin/DatabaseHostFormRequest.php index 7fdd0cdc32..2e581478ec 100644 --- a/app/Http/Requests/Admin/DatabaseHostFormRequest.php +++ b/app/Http/Requests/Admin/DatabaseHostFormRequest.php @@ -3,13 +3,11 @@ namespace Pterodactyl\Http\Requests\Admin; use Pterodactyl\Models\DatabaseHost; +use Illuminate\Contracts\Validation\Validator; class DatabaseHostFormRequest extends AdminFormRequest { - /** - * @return mixed - */ - public function rules() + public function rules(): array { if ($this->method() !== 'POST') { return DatabaseHost::getRulesForUpdate($this->route()->parameter('host')); @@ -20,10 +18,8 @@ public function rules() /** * Modify submitted data before it is passed off to the validator. - * - * @return \Illuminate\Contracts\Validation\Validator */ - protected function getValidatorInstance() + protected function getValidatorInstance(): Validator { if (!$this->filled('node_id')) { $this->merge(['node_id' => null]); diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index 065abaf133..561ffb7978 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -13,16 +6,15 @@ class EggFormRequest extends AdminFormRequest { - /** - * {@inheritdoc} - */ - public function rules() + public function rules(): array { $rules = [ 'name' => 'required|string|max:191', 'description' => 'nullable|string', - 'docker_images' => 'required|string', + 'docker_images' => ['required', 'string', 'regex:/^[\w#\.\/\- ]*\|?~?[\w\.\/\-:@ ]*$/im'], + 'force_outgoing_ip' => 'sometimes|boolean', 'file_denylist' => 'array', + 'features' => 'sometimes|array', 'startup' => 'required|string', 'config_from' => 'sometimes|bail|nullable|numeric', 'config_stop' => 'required_without:config_from|nullable|string|max:191', @@ -38,13 +30,20 @@ public function rules() return $rules; } - /** - * @param \Illuminate\Contracts\Validation\Validator $validator - */ public function withValidator($validator) { $validator->sometimes('config_from', 'exists:eggs,id', function () { return (int) $this->input('config_from') !== 0; }); } + + public function validated($key = null, $default = null): array + { + $data = parent::validated(); + + return array_merge($data, [ + 'force_outgoing_ip' => array_get($data, 'force_outgoing_ip', false), + 'features' => array_get($data, 'features', []), + ]); + } } diff --git a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php index b6adb768eb..7718480e93 100644 --- a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -13,10 +6,7 @@ class EggImportFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { $rules = [ 'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain', diff --git a/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php index 3f522e96fa..b93a63c63c 100644 --- a/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -14,11 +7,9 @@ class EggScriptFormRequest extends AdminFormRequest { /** - * Return the rules to be used when validating the sent data in the request. - * - * @return array + * Return the rules to be used when validating the data sent in the request. */ - public function rules() + public function rules(): array { return [ 'script_install' => 'sometimes|nullable|string', diff --git a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php index d52fe94d2c..d232254eab 100644 --- a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -9,10 +9,8 @@ class EggVariableFormRequest extends AdminFormRequest { /** * Define rules for validation of this request. - * - * @return array */ - public function rules() + public function rules(): array { return [ 'name' => 'required|string|min:1|max:191', diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php index 2ad202f9c3..9625f083ad 100644 --- a/app/Http/Requests/Admin/LocationFormRequest.php +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin; @@ -14,14 +7,12 @@ class LocationFormRequest extends AdminFormRequest { /** - * Setup the validation rules to use for these requests. - * - * @return array + * Set up the validation rules to use for these requests. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { - return Location::getRulesForUpdate($this->route()->parameter('location')->id); + return Location::getRulesForUpdate($this->route()->parameter('location')->id); // @phpstan-ignore property.nonObject } return Location::getRules(); diff --git a/app/Http/Requests/Admin/MountFormRequest.php b/app/Http/Requests/Admin/MountFormRequest.php index bd94a633a5..995c9479c9 100644 --- a/app/Http/Requests/Admin/MountFormRequest.php +++ b/app/Http/Requests/Admin/MountFormRequest.php @@ -7,14 +7,12 @@ class MountFormRequest extends AdminFormRequest { /** - * Setup the validation rules to use for these requests. - * - * @return array + * Set up the validation rules to use for these requests. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { - return Mount::getRulesForUpdate($this->route()->parameter('mount')->id); + return Mount::getRulesForUpdate($this->route()->parameter('mount')->id); // @phpstan-ignore property.nonObject } return Mount::getRules(); diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 2f01dfe9e6..81505de154 100644 --- a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Nest; @@ -13,13 +6,10 @@ class StoreNestFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ - 'name' => 'required|string|min:1|max:191', + 'name' => 'required|string|min:1|max:191|regex:/^[\w\- ]+$/', 'description' => 'string|nullable', ]; } diff --git a/app/Http/Requests/Admin/NewUserFormRequest.php b/app/Http/Requests/Admin/NewUserFormRequest.php new file mode 100644 index 0000000000..d8c1993801 --- /dev/null +++ b/app/Http/Requests/Admin/NewUserFormRequest.php @@ -0,0 +1,28 @@ +only([ + 'email', + 'username', + 'name_first', + 'name_last', + 'password', + 'language', + 'root_admin', + ])->toArray(); + } +} diff --git a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php index 2552114abf..f388203a05 100644 --- a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; @@ -13,10 +6,7 @@ class AllocationAliasFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'alias' => 'present|nullable|string', diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php index 3c580c0264..641b8d08a1 100644 --- a/app/Http/Requests/Admin/Node/AllocationFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; @@ -13,10 +6,7 @@ class AllocationFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'allocation_ip' => 'required|string', diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php index a122467838..f95189f295 100644 --- a/app/Http/Requests/Admin/Node/NodeFormRequest.php +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -1,14 +1,8 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; +use Pterodactyl\Rules\Fqdn; use Pterodactyl\Models\Node; use Pterodactyl\Http\Requests\Admin\AdminFormRequest; @@ -17,32 +11,15 @@ class NodeFormRequest extends AdminFormRequest /** * Get rules to apply to data in this request. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { return Node::getRulesForUpdate($this->route()->parameter('node')); } - return Node::getRules(); - } - - /** - * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator($validator) - { - $validator->after(function ($validator) { - // Check that the FQDN is a valid IP address. - if (!filter_var(gethostbyname($this->input('fqdn')), FILTER_VALIDATE_IP)) { - $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_not_resolvable')); - } + $data = Node::getRules(); + $data['fqdn'][] = Fqdn::make('scheme'); - // Check that if using HTTPS the FQDN is not an IP address. - if (filter_var($this->input('fqdn'), FILTER_VALIDATE_IP) && $this->input('scheme') === 'https') { - $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_required_for_ssl')); - } - }); + return $data; } } diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index e20210b355..bf461dc956 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -4,15 +4,14 @@ use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; class ServerFormRequest extends AdminFormRequest { /** * Rules to be applied to this request. - * - * @return array */ - public function rules() + public function rules(): array { $rules = Server::getRules(); $rules['description'][] = 'nullable'; @@ -23,14 +22,12 @@ public function rules() /** * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator */ - public function withValidator($validator) + public function withValidator(Validator $validator): void { $validator->after(function ($validator) { $validator->sometimes('node_id', 'required|numeric|bail|exists:nodes,id', function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); $validator->sometimes('allocation_id', [ @@ -42,7 +39,7 @@ public function withValidator($validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); $validator->sometimes('allocation_additional.*', [ @@ -54,7 +51,7 @@ public function withValidator($validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); }); } diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index c53825ffe7..17608d9f27 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -7,11 +7,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest { /** - * Return all of the rules to apply to this request's data. - * - * @return array + * Return all the rules to apply to this request's data. */ - public function rules() + public function rules(): array { return [ 'recaptcha:enabled' => 'required|in:true,false', @@ -36,10 +34,7 @@ public function rules() ]; } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'recaptcha:enabled' => 'reCAPTCHA Enabled', diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php index 05d0f9d486..8a24dd0ac8 100644 --- a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -10,10 +10,7 @@ class BaseSettingsFormRequest extends AdminFormRequest { use AvailableLanguages; - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'app:name' => 'required|string|max:191', @@ -22,10 +19,7 @@ public function rules() ]; } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'app:name' => 'Company Name', diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php index 0ad24181ab..9836980703 100644 --- a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -9,17 +9,15 @@ class MailSettingsFormRequest extends AdminFormRequest { /** * Return rules to validate mail settings POST data against. - * - * @return array */ - public function rules() + public function rules(): array { return [ - 'mail:host' => 'required|string', - 'mail:port' => 'required|integer|between:1,65535', - 'mail:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])], - 'mail:username' => 'nullable|string|max:191', - 'mail:password' => 'nullable|string|max:191', + 'mail:mailers:smtp:host' => 'required|string', + 'mail:mailers:smtp:port' => 'required|integer|between:1,65535', + 'mail:mailers:smtp:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])], + 'mail:mailers:smtp:username' => 'nullable|string|max:191', + 'mail:mailers:smtp:password' => 'nullable|string|max:191', 'mail:from:address' => 'required|string|email', 'mail:from:name' => 'nullable|string|max:191', ]; @@ -28,17 +26,13 @@ public function rules() /** * Override the default normalization function for this type of request * as we need to accept empty values on the keys. - * - * @param array $only - * - * @return array */ - public function normalize(array $only = null) + public function normalize(?array $only = null): array { $keys = array_flip(array_keys($this->rules())); - if (empty($this->input('mail:password'))) { - unset($keys['mail:password']); + if (empty($this->input('mail:mailers:smtp:password'))) { + unset($keys['mail:mailers:smtp:password']); } return $this->only(array_flip($keys)); diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php index 4203e65d9c..ae5b5f346f 100644 --- a/app/Http/Requests/Admin/UserFormRequest.php +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -11,7 +11,7 @@ class UserFormRequest extends AdminFormRequest * Rules to apply to requests for updating or creating a user * in the Admin CP. */ - public function rules() + public function rules(): array { return Collection::make( User::getRulesForUpdate($this->route()->parameter('user')) diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 9fab4e2c37..6529a9a5af 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -2,38 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Allocation; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteAllocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested allocation exists and belongs to the node that - * is being passed in the URL. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - $allocation = $this->route()->parameter('allocation'); - - if ($node instanceof Node && $node->exists) { - if ($allocation instanceof Allocation && $allocation->exists && $allocation->node_id === $node->id) { - return true; - } - } - - return false; - } + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index 524e201e1c..f03223f2d2 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -2,30 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetAllocationsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; - - /** - * Determine if the node that we are requesting the allocations - * for exists on the Panel. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index d1f5415a6a..a7e0c4da2d 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -7,15 +7,9 @@ class StoreAllocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; public function rules(): array { @@ -27,10 +21,7 @@ public function rules(): array ]; } - /** - * @return array - */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 11deab45b5..bddbfba0fa 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -2,45 +2,34 @@ namespace Pterodactyl\Http\Requests\Api\Application; +use Webmozart\Assert\Assert; use Pterodactyl\Models\ApiKey; +use Laravel\Sanctum\TransientToken; +use Illuminate\Validation\Validator; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Foundation\Http\FormRequest; use Pterodactyl\Exceptions\PterodactylException; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Routing\Exception\InvalidParameterException; abstract class ApplicationApiRequest extends FormRequest { - /** - * Tracks if the request has been validated internally or not to avoid - * making duplicate validation calls. - * - * @var bool - */ - private $hasValidated = false; - /** * The resource that should be checked when performing the authorization * function for this request. - * - * @var string|null */ - protected $resource; + protected ?string $resource; /** * The permission level that a given API key should have for accessing * the defined $resource during the request cycle. - * - * @var int */ - protected $permission = AdminAcl::NONE; + protected int $permission = AdminAcl::NONE; /** * Determine if the current user is authorized to perform * the requested action against the API. * - * @throws \Pterodactyl\Exceptions\PterodactylException + * @throws PterodactylException */ public function authorize(): bool { @@ -48,15 +37,16 @@ public function authorize(): bool throw new PterodactylException('An ACL resource must be defined on API requests.'); } - return AdminAcl::check($this->key(), $this->resource, $this->permission); - } + $token = $this->user()->currentAccessToken(); + if ($token instanceof TransientToken) { // @phpstan-ignore instanceof.alwaysFalse + return true; + } - /** - * Determine if the requested resource exists on the server. - */ - public function resourceExists(): bool - { - return true; + if ($token->key_type === ApiKey::TYPE_ACCOUNT) { + return true; + } + + return AdminAcl::check($token, $this->resource, $this->permission); } /** @@ -68,79 +58,36 @@ public function rules(): array } /** - * Return the API key being used for the request. + * Helper method allowing a developer to easily hook into this logic without having + * to remember what the method name is called or where to use it. By default this is + * a no-op. */ - public function key(): ApiKey + public function withValidator(Validator $validator): void { - return $this->attributes->get('api_key'); + // do nothing } /** - * Grab a model from the route parameters. If no model is found in the - * binding mappings an exception will be thrown. - * - * @return mixed + * Returns the named route parameter and asserts that it is a real model that + * exists in the database. * - * @deprecated - * - * @throws \Symfony\Component\Routing\Exception\InvalidParameterException - */ - public function getModel(string $model) - { - $parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model); - - if (is_null($parameterKey)) { - throw new InvalidParameterException(); - } - - return $this->route()->parameter($parameterKey); - } - - /** - * Validate that the resource exists and can be accessed prior to booting - * the validator and attempting to use the data. + * @template T of \Illuminate\Database\Eloquent\Model * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - protected function prepareForValidation() - { - if (!$this->passesAuthorization()) { - $this->failedAuthorization(); - } - - $this->hasValidated = true; - } - - /* - * Determine if the request passes the authorization check as well - * as the exists check. + * @param class-string $expect * - * @return bool + * @return T * - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @noinspection PhpDocSignatureInspection */ - protected function passesAuthorization() + public function parameter(string $key, string $expect) { - // If we have already validated we do not need to call this function - // again. This is needed to work around Laravel's normal auth validation - // that occurs after validating the request params since we are doing auth - // validation in the prepareForValidation() function. - if ($this->hasValidated) { - return true; - } - - if (!parent::passesAuthorization()) { - return false; - } + $value = $this->route()->parameter($key); - // Only let the user know that a resource does not exist if they are - // authenticated to access the endpoint. This avoids exposing that - // an item exists (or does not exist) to the user until they can prove - // that they have permission to know about it. - if ($this->attributes->get('is_missing_model', false) || !$this->resourceExists()) { - throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); - } + Assert::isInstanceOf($value, $expect); + Assert::isInstanceOf($value, Model::class); // @phpstan-ignore staticMethod.alreadyNarrowedType + Assert::true($value->exists); - return true; + /* @var T $value */ + return $value; } } diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index ac58314f9c..3c41e11663 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -2,29 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Models\Location; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteLocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php index f7c10e0c00..dea82db335 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php @@ -2,17 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Models\Location; - class GetLocationRequest extends GetLocationsRequest { - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php index 5edf004629..65157dc475 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -7,13 +7,7 @@ class GetLocationsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index c5586ead11..cf0b126299 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -8,15 +8,9 @@ class StoreLocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Rules to validate the request against. @@ -31,10 +25,8 @@ public function rules(): array /** * Rename fields to be more clear in error messages. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'long' => 'Location Description', diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index c775e2b1b1..afda4a15bb 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -6,22 +6,12 @@ class UpdateLocationRequest extends StoreLocationRequest { - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } - /** * Rules to validate this request against. */ public function rules(): array { - $locationId = $this->route()->parameter('location')->id; + $locationId = $this->route()->parameter('location')->id; // @phpstan-ignore property.nonObject return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php index 80304ab279..7c6eb54121 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php @@ -2,28 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs; -use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetEggRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_EGGS; + protected ?string $resource = AdminAcl::RESOURCE_EGGS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; - - /** - * Determine if the requested egg exists for the selected nest. - */ - public function resourceExists(): bool - { - return $this->getModel(Nest::class)->id === $this->getModel(Egg::class)->nest_id; - } + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php index a6aadf9043..b504af5b85 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php @@ -7,13 +7,7 @@ class GetEggsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_EGGS; + protected ?string $resource = AdminAcl::RESOURCE_EGGS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php index b90c457489..b4a990f9f7 100644 --- a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php +++ b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php @@ -7,13 +7,7 @@ class GetNestsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NESTS; + protected ?string $resource = AdminAcl::RESOURCE_NESTS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index eb1d1d9a52..01f503f3f0 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -2,30 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteNodeRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; - - /** - * Determine if the node being requested for editing exists - * on the Panel before validating the data. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php index 0eae14008f..fd077ecd5b 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php @@ -4,9 +4,6 @@ class GetDeployableNodesRequest extends GetNodesRequest { - /** - * @return string[] - */ public function rules(): array { return [ diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php index bbb1570352..6d231bc97d 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php @@ -2,17 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; - class GetNodeRequest extends GetNodesRequest { - /** - * Determine if the requested node exists on the Panel. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php index fc5f5a38e9..5c8524b945 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -7,13 +7,7 @@ class GetNodesRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 8c05c5a37e..236982426d 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -8,28 +8,24 @@ class StoreNodeRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules to apply to this request. */ - public function rules(array $rules = null): array + public function rules(?array $rules = null): array { return collect($rules ?? Node::getRules())->only([ 'public', 'name', + 'description', 'location_id', 'fqdn', 'scheme', 'behind_proxy', + 'maintenance_mode', 'memory', 'memory_overallocate', 'disk', @@ -47,10 +43,8 @@ public function rules(array $rules = null): array /** * Fields to rename for clarity in the API response. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'daemon_base' => 'Daemon Base Path', @@ -63,10 +57,8 @@ public function attributes() /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $response = parent::validated(); $response['daemonListen'] = $response['daemon_listen']; diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index 2da2d40611..a968232fed 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -10,10 +10,10 @@ class UpdateNodeRequest extends StoreNodeRequest * Apply validation rules to this request. Uses the parent class rules() * function but passes in the rules for updating rather than creating. */ - public function rules(array $rules = null): array + public function rules(?array $rules = null): array { - $nodeId = $this->getModel(Node::class)->id; + $node = $this->parameter('node', Node::class); - return parent::rules(Node::getRulesForUpdate($nodeId)); + return parent::rules(Node::getRulesForUpdate($node)); } } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 2dff1374e4..01df4af328 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -7,24 +7,7 @@ class GetServerDatabaseRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; - - /** - * Determine if the requested server database exists. - */ - public function resourceExists(): bool - { - $server = $this->route()->parameter('server'); - $database = $this->route()->parameter('database'); - - return $database->server_id === $server->id; - } + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php index 3e6cfc6fe6..ce72bbc20f 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -7,13 +7,7 @@ class GetServerDatabasesRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php index 917a5313fc..66cec82c39 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php @@ -6,8 +6,5 @@ class ServerDatabaseWriteRequest extends GetServerDatabasesRequest { - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index c37de870d8..678f90e9d3 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -12,22 +12,16 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules for database creation. */ public function rules(): array { - $server = $this->route()->parameter('server'); + $server = $this->parameter('server', Server::class); return [ 'database' => [ @@ -46,10 +40,8 @@ public function rules(): array /** * Return data formatted in the correct format for the service to consume. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { return [ 'database' => $this->input('database'), @@ -60,10 +52,8 @@ public function validated() /** * Format error messages in a more understandable format for API output. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'host' => 'Database Host Server ID', diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index 902bc60c51..50c9dabf82 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -2,52 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalServerRequest extends ApplicationApiRequest { - /** - * @var \Pterodactyl\Models\Server - */ - private $serverModel; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; - - /** - * @var int - */ - protected $permission = AdminAcl::READ; - - /** - * Determine if the requested external user exists. - */ - public function resourceExists(): bool - { - $repository = $this->container->make(ServerRepositoryInterface::class); - - try { - $this->serverModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - /** - * Return the server model for the requested external server. - */ - public function getServerModel(): Server - { - return $this->serverModel; - } + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php index 82d12687ca..63c4ea86a2 100644 --- a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -7,13 +7,7 @@ class GetServerRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php index 07c2013365..df2d76cd39 100644 --- a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -7,13 +7,7 @@ class ServerWriteRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index dd24e1efc5..a9d0ecbed9 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -4,22 +4,16 @@ use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Contracts\Validation\Validator; use Pterodactyl\Models\Objects\DeploymentObject; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Rules to be applied to this request. @@ -73,10 +67,8 @@ public function rules(): array /** * Normalize the data into a format that can be consumed by the service. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -102,15 +94,16 @@ public function validated() 'database_limit' => array_get($data, 'feature_limits.databases'), 'allocation_limit' => array_get($data, 'feature_limits.allocations'), 'backup_limit' => array_get($data, 'feature_limits.backups'), + 'oom_disabled' => array_get($data, 'oom_disabled'), ]; } /* * Run validation after the rules above have been applied. * - * @param \Illuminate\Contracts\Validation\Validator $validator + * @param \Illuminate\Validation\Validator $validator */ - public function withValidator(Validator $validator) + public function withValidator(Validator $validator): void { $validator->sometimes('allocation.default', [ 'required', 'integer', 'bail', @@ -118,7 +111,7 @@ public function withValidator(Validator $validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->deploy); + return !$input->deploy; }); $validator->sometimes('allocation.additional.*', [ @@ -127,7 +120,7 @@ public function withValidator(Validator $validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->deploy); + return !$input->deploy; }); $validator->sometimes('deploy.locations', 'present', function ($input) { @@ -141,10 +134,8 @@ public function withValidator(Validator $validator) /** * Return a deployment object that can be passed to the server creation service. - * - * @return \Pterodactyl\Models\Objects\DeploymentObject|null */ - public function getDeploymentObject() + public function getDeploymentObject(): ?DeploymentObject { if (is_null($this->input('deploy'))) { return null; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 3f534b08a7..f1c977f11f 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -12,7 +12,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->getModel(Server::class)); + $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'allocation' => $rules['allocation_id'], @@ -51,10 +51,8 @@ public function rules(): array /** * Convert the allocation field into the expected format for the service handler. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -78,10 +76,8 @@ public function validated() /** * Custom attributes to use in error message responses. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'add_allocations' => 'allocations to add', @@ -99,11 +95,9 @@ public function attributes() * compatability with the old API endpoint while also supporting a more correct API * call. * - * @return array - * * @see https://github.com/pterodactyl/panel/issues/1500 */ - protected function requiredToOptional(string $field, array $rules, bool $limits = false) + protected function requiredToOptional(string $field, array $rules, bool $limits = false): array { if (!in_array('required', $rules)) { return $rules; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index 06bd22f5fd..aecc1cf02c 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -11,7 +11,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->getModel(Server::class)); + $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'external_id' => $rules['external_id'], @@ -25,7 +25,7 @@ public function rules(): array * Convert the posted data into the correct format that is expected * by the application. */ - public function validated(): array + public function validated($key = null, $default = null): array { return [ 'external_id' => $this->input('external_id'), @@ -36,7 +36,7 @@ public function validated(): array } /** - * Rename some of the attributes in error messages to clarify the field + * Rename some attributes in error messages to clarify the field * being discussed. */ public function attributes(): array diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index a873e01980..985b10a6f6 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -8,22 +8,16 @@ class UpdateServerStartupRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules to run the input against. */ public function rules(): array { - $data = Server::getRulesForUpdate($this->getModel(Server::class)); + $data = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'startup' => $data['startup'], @@ -36,10 +30,8 @@ public function rules(): array /** * Return the validated data in a format that is expected by the service. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index 56c028a6e6..5e840a1c02 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -2,29 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteUserRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested user exists on the Panel. - */ - public function resourceExists(): bool - { - $user = $this->route()->parameter('user'); - - return $user instanceof User && $user->exists; - } + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index 5f63d04ba6..0f44aed3ff 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -2,52 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalUserRequest extends ApplicationApiRequest { - /** - * @var User - */ - private $userModel; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; - - /** - * @var int - */ - protected $permission = AdminAcl::READ; - - /** - * Determine if the requested external user exists. - */ - public function resourceExists(): bool - { - $repository = $this->container->make(UserRepositoryInterface::class); - - try { - $this->userModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - /** - * Return the user model for the requested external user. - */ - public function getUserModel(): User - { - return $this->userModel; - } + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php index 8736a8e9db..71ea30d338 100644 --- a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php @@ -7,13 +7,7 @@ class GetUsersRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = Acl::RESOURCE_USERS; + protected ?string $resource = Acl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = Acl::READ; + protected int $permission = Acl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php index 10a2d6b281..9d4a98561c 100644 --- a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php @@ -8,20 +8,14 @@ class StoreUserRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Return the validation rules for this request. */ - public function rules(array $rules = null): array + public function rules(?array $rules = null): array { $rules = $rules ?? User::getRules(); @@ -40,10 +34,7 @@ public function rules(array $rules = null): array return $response; } - /** - * @return array - */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -57,10 +48,8 @@ public function validated() /** * Rename some fields to be more user friendly. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'external_id' => 'Third Party Identifier', diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index b341eaa241..b31a3c52c2 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -9,9 +9,9 @@ class UpdateUserRequest extends StoreUserRequest /** * Return the validation rules for this request. */ - public function rules(array $rules = null): array + public function rules(?array $rules = null): array { - $userId = $this->getModel(User::class)->id; + $userId = $this->parameter('user', User::class)->id; return parent::rules(User::getRulesForUpdate($userId)); } diff --git a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php index c2a21c4c0f..efa1cc3cd7 100644 --- a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php @@ -2,7 +2,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; +use IPTools\Range; use Pterodactyl\Models\ApiKey; +use Illuminate\Validation\Validator; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class StoreApiKeyRequest extends ClientApiRequest @@ -13,18 +15,33 @@ public function rules(): array return [ 'description' => $rules['memo'], - 'allowed_ips' => $rules['allowed_ips'], - 'allowed_ips.*' => 'ip', + 'allowed_ips' => [...$rules['allowed_ips'], 'max:50'], + 'allowed_ips.*' => 'string', ]; } /** - * @return array|string[] + * Check that each of the values entered is actually valid. */ - public function messages() + public function withValidator(Validator $validator): void { - return [ - 'allowed_ips.*' => 'All of the IP addresses entered must be valid IPv4 addresses.', - ]; + $validator->after(function (Validator $validator) { + if (!is_array($ips = $this->input('allowed_ips'))) { + return; + } + + foreach ($ips as $index => $ip) { + $valid = false; + try { + $valid = Range::parse($ip)->valid(); + } catch (\Exception $exception) { + if ($exception->getMessage() !== 'Invalid IP address format') { + throw $exception; + } + } finally { + $validator->errors()->addIf(!$valid, "allowed_ips.{$index}", '"' . $ip . '" is not a valid IP address or CIDR range.'); + } + } + }); } } diff --git a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php new file mode 100644 index 0000000000..3c8cf89ed5 --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php @@ -0,0 +1,78 @@ + UserSSHKey::getRulesForField('name'), + 'public_key' => UserSSHKey::getRulesForField('public_key'), + ]; + } + + /** + * Check to see if this SSH key has already been added to the user's account + * and if so return an error. + */ + public function withValidator(Validator $validator): void + { + $validator->after(function () { + try { + $this->key = PublicKeyLoader::loadPublicKey($this->input('public_key')); + } catch (NoKeyLoadedException $exception) { + $this->validator->errors()->add('public_key', 'The public key provided is not valid.'); + + return; + } + + if ($this->key instanceof DSA) { + $this->validator->errors()->add('public_key', 'DSA keys are not supported.'); + } + + if ($this->key instanceof RSA && $this->key->getLength() < 2048) { + $this->validator->errors()->add('public_key', 'RSA keys must be at least 2048 bytes in length.'); + } + + $fingerprint = $this->key->getFingerprint('sha256'); + if ($this->user()->sshKeys()->where('fingerprint', $fingerprint)->exists()) { + $this->validator->errors()->add('public_key', 'The public key provided already exists on your account.'); + } + }); + } + + /** + * Returns the public key but formatted in a consistent manner. + */ + public function getPublicKey(): string + { + return $this->key->toString('PKCS8'); + } + + /** + * Returns the SHA256 fingerprint of the key provided. + */ + public function getKeyFingerprint(): string + { + if (!$this->key) { + throw new \Exception('The public key was not properly loaded for this request.'); + } + + return $this->key->getFingerprint('sha256'); + } +} diff --git a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php index 6287ba5855..abafc2dc6a 100644 --- a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php @@ -11,7 +11,7 @@ class UpdateEmailRequest extends ClientApiRequest { /** - * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException + * @throws InvalidPasswordProvidedException */ public function authorize(): bool { diff --git a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php index de8215b07e..8f003d0282 100644 --- a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php @@ -10,7 +10,7 @@ class UpdatePasswordRequest extends ClientApiRequest { /** - * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException + * @throws InvalidPasswordProvidedException */ public function authorize(): bool { diff --git a/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php new file mode 100644 index 0000000000..d2d427f99f --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php @@ -0,0 +1,19 @@ + 'required|boolean']; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php index 5fbdaf728d..2871c039cf 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php @@ -7,10 +7,7 @@ class StoreBackupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_BACKUP_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php index 92b14157a5..eb2cbc57e5 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; @@ -14,9 +12,4 @@ public function permission(): string { return Permission::ACTION_DATABASE_DELETE; } - - public function resourceExists(): bool - { - return $this->getModel(Server::class)->id === $this->getModel(Database::class)->server_id; - } } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php index b8f12bd8ac..be4f4a719d 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php @@ -5,6 +5,7 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; use Illuminate\Database\Query\Builder; use Pterodactyl\Contracts\Http\ClientPermissionsRequest; @@ -28,7 +29,7 @@ public function rules(): array 'database' => [ 'required', 'alpha_dash', - 'min:1', + 'min:3', 'max:48', // Yes, I am aware that you could have the same database name across two unique hosts. However, // I don't really care about that for this validation. We just want to make sure it is unique to @@ -38,14 +39,11 @@ public function rules(): array ->where('database', DatabaseManagementService::generateUniqueDatabaseName($this->input('database'), $server->id)); }), ], - 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + 'remote' => Database::getRulesForField('remote'), ]; } - /** - * @return array - */ - public function messages() + public function messages(): array { return [ 'database.unique' => 'The database name you have selected is already in use by this server.', diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php index 0386b8555d..c588c9b23f 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php @@ -13,6 +13,6 @@ class DownloadFileRequest extends ClientApiRequest */ public function authorize(): bool { - return $this->user()->can('file.read', $this->getModel(Server::class)); + return $this->user()->can('file.read', $this->parameter('server', Server::class)); } } diff --git a/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php index cc66d69b83..25443148f4 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php @@ -9,7 +9,7 @@ class ListFilesRequest extends ClientApiRequest { /** * Check that the user making this request to the API is authorized to list all - * of the files that exist for a given server. + * the files that exist for a given server. */ public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php index ad3d578977..5f76482476 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php @@ -13,14 +13,14 @@ public function permission(): string return Permission::ACTION_FILE_CREATE; } - /** - * @return string[] - */ public function rules(): array { return [ 'url' => 'required|string|url', - 'directory' => 'sometimes|nullable|string', + 'directory' => 'nullable|string', + 'filename' => 'nullable|string', + 'use_header' => 'boolean', + 'foreground' => 'boolean', ]; } } diff --git a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php index 6808a54978..a591fdf68d 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php @@ -7,10 +7,7 @@ class UploadFileRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_FILE_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php index 811b87dfb5..5ceb7c8e06 100644 --- a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php @@ -23,6 +23,7 @@ public function rules(): array 'payload' => 'required_unless:action,backup|string|nullable', 'time_offset' => 'required|numeric|min:0|max:900', 'sequence_id' => 'sometimes|required|numeric|min:1', + 'continue_on_failure' => 'sometimes|required|boolean', ]; } } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php index 9edc8ad1c6..63f01dbae4 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php @@ -7,10 +7,7 @@ class ReinstallServerRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_SETTINGS_REINSTALL; } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php index 8cb5efa35c..4a74f0a0e6 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php @@ -11,7 +11,7 @@ class RenameServerRequest extends ClientApiRequest implements ClientPermissionsR { /** * Returns the permissions string indicating which permission should be used to - * validate that the authenticated user has permission to perform this action aganist + * validate that the authenticated user has permission to perform this action against * the given resource (server). */ public function permission(): string @@ -26,6 +26,7 @@ public function rules(): array { return [ 'name' => Server::getRules()['name'], + 'description' => 'string|nullable', ]; } } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index bd3a1e65fb..2d4e182e0f 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -16,18 +16,14 @@ public function permission(): string return Permission::ACTION_STARTUP_DOCKER_IMAGE; } - /** - * @return array[] - */ public function rules(): array { - /** @var \Pterodactyl\Models\Server $server */ $server = $this->route()->parameter('server'); Assert::isInstanceOf($server, Server::class); return [ - 'docker_image' => ['required', 'string', Rule::in($server->egg->docker_images)], + 'docker_image' => ['required', 'string', 'max:191', 'regex:/^[\w#\.\/\- ]*\|?~?[\w\.\/\-:@ ]*$/', Rule::in(array_values($server->egg->docker_images))], ]; } } diff --git a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php index 25ab2ce21a..fee92bcbcb 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php @@ -7,10 +7,7 @@ class GetStartupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_READ; } diff --git a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php index b46e6ea9a1..2c32f4df6d 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php @@ -7,18 +7,13 @@ class UpdateStartupVariableRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_UPDATE; } /** * The actual validation of the variable's value will happen inside the controller. - * - * @return array|string[] */ public function rules(): array { diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php index ef0a09aaaf..eabd84e616 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php @@ -6,10 +6,7 @@ class DeleteSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_DELETE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php index 5bc93ab2fb..28accf20df 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php @@ -6,10 +6,7 @@ class StoreSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_CREATE; } @@ -17,7 +14,7 @@ public function permission() public function rules(): array { return [ - 'email' => 'required|email|between:1,191', + 'email' => 'required|email:strict|between:1,191', 'permissions' => 'required|array', 'permissions.*' => 'string', ]; diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php index 5a20084d3f..3e60ac52ce 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php @@ -4,16 +4,14 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Pterodactyl\Models\Subuser; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Services\Servers\GetUserPermissionsService; abstract class SubuserRequest extends ClientApiRequest { - /** - * @var \Pterodactyl\Models\Subuser|null - */ - protected $model; + protected ?Subuser $model; /** * Authorize the request and ensure that a user is not trying to modify themselves. @@ -65,8 +63,6 @@ protected function validatePermissionsCanBeAssigned(array $permissions) // Otherwise, get the current subuser's permission set, and ensure that the // permissions they are trying to assign are not _more_ than the ones they // already have. - /** @var \Pterodactyl\Models\Subuser|null $subuser */ - /** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */ $service = $this->container->make(GetUserPermissionsService::class); if (count(array_diff($permissions, $service->handle($server, $user))) > 0) { diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php index 997b2daee2..bd8929a98d 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -6,10 +6,7 @@ class UpdateSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_UPDATE; } diff --git a/app/Http/Requests/Api/Remote/ActivityEventRequest.php b/app/Http/Requests/Api/Remote/ActivityEventRequest.php new file mode 100644 index 0000000000..43f527dfa6 --- /dev/null +++ b/app/Http/Requests/Api/Remote/ActivityEventRequest.php @@ -0,0 +1,50 @@ + ['required', 'array'], + 'data.*' => ['array'], + 'data.*.user' => ['sometimes', 'nullable', 'uuid'], + 'data.*.server' => ['required', 'uuid'], + 'data.*.event' => ['required', 'string'], + 'data.*.metadata' => ['present', 'nullable', 'array'], + 'data.*.ip' => ['sometimes', 'nullable', 'ip'], + 'data.*.timestamp' => ['required', 'string'], + ]; + } + + /** + * Returns all the unique server UUIDs that were received in this request. + */ + public function servers(): array + { + return Collection::make($this->input('data'))->pluck('server')->unique()->toArray(); + } + + /** + * Returns all the unique user UUIDs that were submitted in this request. + */ + public function users(): array + { + return Collection::make($this->input('data')) + ->filter(function ($value) { + return !empty($value['user']); + }) + ->pluck('user') + ->unique() + ->toArray(); + } +} diff --git a/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php index 885e192397..1bae01dd7f 100644 --- a/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php +++ b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php @@ -6,18 +6,12 @@ class AuthenticateWebsocketDetailsRequest extends FormRequest { - /** - * @return bool - */ - public function authorize() + public function authorize(): bool { return true; } - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'server_uuid' => 'required|string', diff --git a/app/Http/Requests/Api/Remote/InstallationDataRequest.php b/app/Http/Requests/Api/Remote/InstallationDataRequest.php index 0737d71b4e..13b3e77d7a 100644 --- a/app/Http/Requests/Api/Remote/InstallationDataRequest.php +++ b/app/Http/Requests/Api/Remote/InstallationDataRequest.php @@ -6,21 +6,16 @@ class InstallationDataRequest extends FormRequest { - /** - * @return bool - */ - public function authorize() + public function authorize(): bool { return true; } - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'successful' => 'present|boolean', + 'reinstall' => 'sometimes|boolean', ]; } } diff --git a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php index 0c96b3f02a..d0dd3090bb 100644 --- a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php +++ b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php @@ -6,16 +6,16 @@ class ReportBackupCompleteRequest extends FormRequest { - /** - * @return string[] - */ - public function rules() + public function rules(): array { return [ 'successful' => 'required|boolean', 'checksum' => 'nullable|string|required_if:successful,true', 'checksum_type' => 'nullable|string|required_if:successful,true', 'size' => 'nullable|numeric|required_if:successful,true', + 'parts' => 'nullable|array', + 'parts.*.etag' => 'required|string', + 'parts.*.part_number' => 'required|numeric', ]; } } diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php index 041ff197fe..964c27974b 100644 --- a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -8,34 +8,29 @@ class SftpAuthenticationFormRequest extends FormRequest { /** * Authenticate the request. - * - * @return bool */ - public function authorize() + public function authorize(): bool { return true; } /** * Rules to apply to the request. - * - * @return array */ - public function rules() + public function rules(): array { return [ - 'username' => 'required|string', - 'password' => 'required|string', + 'type' => ['nullable', 'in:password,public_key'], + 'username' => ['required', 'string'], + 'password' => ['required', 'string'], ]; } /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize() + public function normalize(): array { return $this->only( array_keys($this->rules()) diff --git a/app/Http/Requests/Base/LocaleRequest.php b/app/Http/Requests/Base/LocaleRequest.php new file mode 100644 index 0000000000..0a2cc03d72 --- /dev/null +++ b/app/Http/Requests/Base/LocaleRequest.php @@ -0,0 +1,16 @@ + ['required', 'string', 'regex:/^[a-z][a-z]$/'], + 'namespace' => ['required', 'string', 'regex:/^[a-z]{1,191}$/'], + ]; + } +} diff --git a/app/Http/Requests/FrontendUserFormRequest.php b/app/Http/Requests/FrontendUserFormRequest.php index b5553dc16f..66d13d8c54 100644 --- a/app/Http/Requests/FrontendUserFormRequest.php +++ b/app/Http/Requests/FrontendUserFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests; @@ -13,14 +6,12 @@ abstract class FrontendUserFormRequest extends FormRequest { - abstract public function rules(); + abstract public function rules(): array; /** * Determine if a user is authorized to access this endpoint. - * - * @return bool */ - public function authorize() + public function authorize(): bool { return !is_null($this->user()); } @@ -28,10 +19,8 @@ public function authorize() /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize() + public function normalize(): array { return $this->only( array_keys($this->rules()) diff --git a/app/Http/Resources/Wings/ServerConfigurationCollection.php b/app/Http/Resources/Wings/ServerConfigurationCollection.php index fe352301c8..84fc2f8111 100644 --- a/app/Http/Resources/Wings/ServerConfigurationCollection.php +++ b/app/Http/Resources/Wings/ServerConfigurationCollection.php @@ -14,13 +14,9 @@ class ServerConfigurationCollection extends ResourceCollection * Converts a collection of Server models into an array of configuration responses * that can be understood by Wings. Make sure you've properly loaded the required * relationships on the Server models before calling this function, otherwise you'll - * have some serious performance issues from all of the N+1 queries. - * - * @param \Illuminate\Http\Request $request - * - * @return array + * have some serious performance issues from all the N+1 queries. */ - public function toArray($request) + public function toArray($request): array { $egg = Container::getInstance()->make(EggConfigurationService::class); $configuration = Container::getInstance()->make(ServerConfigurationStructureService::class); diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index 902c237b7d..d42f8a80ad 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -7,23 +7,17 @@ class AssetComposer { - /** - * @var \Pterodactyl\Services\Helpers\AssetHashService - */ - private $assetHashService; - /** * AssetComposer constructor. */ - public function __construct(AssetHashService $assetHashService) + public function __construct(private AssetHashService $assetHashService) { - $this->assetHashService = $assetHashService; } /** * Provide access to the asset service in the views. */ - public function compose(View $view) + public function compose(View $view): void { $view->with('asset', $this->assetHashService); $view->with('siteConfiguration', [ diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php deleted file mode 100644 index 0eb64bc7d8..0000000000 --- a/app/Jobs/Job.php +++ /dev/null @@ -1,21 +0,0 @@ -target instanceof Node ? "node:{$this->target->uuid}" : "server:{$this->target->uuid}"; + + return "revoke-sftp:{$this->user}:{$target}"; + } + + public function handle(DaemonRevocationRepository $repository): void + { + $node = $this->target instanceof Node ? $this->target : $this->target->node; + + try { + $repository->setNode($node)->deauthorize( + $this->user, + $this->target instanceof Server ? [$this->target->uuid] : [] + ); + } catch (DaemonConnectionException) { + // Keep retrying this job with a longer and longer backoff until we hit three + // attempts at which point we stop and will assume the node is fully offline + // and we are just wasting time. + $this->release($this->attempts() * 10); + } + } +} diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index fa62bc0043..527b0303ab 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -2,13 +2,10 @@ namespace Pterodactyl\Jobs\Schedule; -use Exception; -use Pterodactyl\Jobs\Job; use Carbon\CarbonImmutable; use Pterodactyl\Models\Task; -use InvalidArgumentException; +use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\DispatchesJobs; use Pterodactyl\Services\Backups\InitiateBackupService; @@ -16,30 +13,18 @@ use Pterodactyl\Repositories\Wings\DaemonCommandRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -class RunTaskJob extends Job implements ShouldQueue +class RunTaskJob implements ShouldQueue { + use Queueable; use DispatchesJobs; - use InteractsWithQueue; use SerializesModels; - /** - * @var \Pterodactyl\Models\Task - */ - public $task; - - /** - * @var bool - */ - public $manualRun; - /** * RunTaskJob constructor. */ - public function __construct(Task $task, $manualRun = false) + public function __construct(public Task $task, public bool $manualRun = false) { - $this->queue = config('pterodactyl.queues.standard'); - $this->task = $task; - $this->manualRun = $manualRun; + $this->queue = 'standard'; } /** @@ -50,7 +35,7 @@ public function __construct(Task $task, $manualRun = false) public function handle( DaemonCommandRepository $commandRepository, InitiateBackupService $backupService, - DaemonPowerRepository $powerRepository + DaemonPowerRepository $powerRepository, ) { // Do not process a task that is not set to active, unless it's been manually triggered. if (!$this->task->schedule->is_active && !$this->manualRun) { @@ -61,6 +46,16 @@ public function handle( } $server = $this->task->server; + // If we made it to this point and the server status is not null it means the + // server was likely suspended or marked as reinstalling after the schedule + // was queued up. Just end the task right now — this should be a very rare + // condition. + if (!is_null($server->status)) { + $this->failed(); + + return; + } + // Perform the provided task against the daemon. try { switch ($this->task->action) { @@ -74,9 +69,9 @@ public function handle( $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); break; default: - throw new InvalidArgumentException('Invalid task action provided: ' . $this->task->action); + throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action); } - } catch (Exception $exception) { + } catch (\Exception $exception) { // If this isn't a DaemonConnectionException on a task that allows for failures // throw the exception back up the chain so that the task is stopped. if (!($this->task->continue_on_failure && $exception instanceof DaemonConnectionException)) { @@ -91,7 +86,7 @@ public function handle( /** * Handle a failure while sending the action to the daemon or otherwise processing the job. */ - public function failed(Exception $exception = null) + public function failed(?\Exception $exception = null) { $this->markTaskNotQueued(); $this->markScheduleComplete(); @@ -102,7 +97,7 @@ public function failed(Exception $exception = null) */ private function queueNextTask() { - /** @var \Pterodactyl\Models\Task|null $nextTask */ + /** @var Task|null $nextTask */ $nextTask = Task::query()->where('schedule_id', $this->task->schedule_id) ->orderBy('sequence_id', 'asc') ->where('sequence_id', '>', $this->task->sequence_id) diff --git a/app/Listeners/AuthenticationListener.php b/app/Listeners/AuthenticationListener.php new file mode 100644 index 0000000000..e9234262e6 --- /dev/null +++ b/app/Listeners/AuthenticationListener.php @@ -0,0 +1,45 @@ +user) { + $activity = $activity->subject($event->user); + } + + if ($event instanceof Failed) { + foreach ($event->credentials as $key => $value) { + $activity = $activity->property($key, $value); + } + } + + $activity->event($event instanceof Failed ? 'auth:fail' : 'auth:success')->log(); + } + + public function reset(PasswordReset $event): void + { + Activity::event('event:password-reset')->withRequestMetadata()->subject($event->user)->log(); + } + + public function subscribe(Dispatcher $events): void + { + $events->listen(Failed::class, [self::class, 'login']); + $events->listen(DirectLogin::class, [self::class, 'login']); + $events->listen(PasswordReset::class, [self::class, 'reset']); + } +} diff --git a/app/Listeners/RevocationListener.php b/app/Listeners/RevocationListener.php new file mode 100644 index 0000000000..0d60d2ed39 --- /dev/null +++ b/app/Listeners/RevocationListener.php @@ -0,0 +1,33 @@ +user; + + // Look at all of the nodes that a user is associated with and trigger a job + // that disconnects them from websockets and SFTP. + Node::query() + ->whereIn('nodes.id', $user->accessibleServers()->select('servers.node_id')->distinct()) + ->chunk(50, function (Collection $nodes) use ($user) { + $nodes->each(fn (Node $node) => RevokeSftpAccessJob::dispatch($user->uuid, $node)); + }); + } + + public function subscribe(Dispatcher $events): void + { + $events->listen(Deleting::class, [self::class, 'revoke']); + $events->listen(PasswordChanged::class, [self::class, 'revoke']); + } +} diff --git a/app/Listeners/TwoFactorListener.php b/app/Listeners/TwoFactorListener.php new file mode 100644 index 0000000000..b1b765783e --- /dev/null +++ b/app/Listeners/TwoFactorListener.php @@ -0,0 +1,24 @@ +recovery ? 'auth:recovery-token' : 'auth:token') + ->withRequestMetadata() + ->subject($event->user) + ->log(); + } + + public function subscribe(Dispatcher $events): void + { + $events->listen(ProvidedAuthenticationToken::class, self::class); + } +} diff --git a/app/Models/APILog.php b/app/Models/APILog.php index 359daa4ed2..673f94e738 100644 --- a/app/Models/APILog.php +++ b/app/Models/APILog.php @@ -8,29 +8,21 @@ class APILog extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'api_logs'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = []; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'authorized' => 'boolean', diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php new file mode 100644 index 0000000000..9ccefe5285 --- /dev/null +++ b/app/Models/ActivityLog.php @@ -0,0 +1,157 @@ + $subjects + * @property int|null $subjects_count + * @property ApiKey|null $apiKey + * + * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) + * @method static Builder|ActivityLog forEvent(string $action) + * @method static Builder|ActivityLog newModelQuery() + * @method static Builder|ActivityLog newQuery() + * @method static Builder|ActivityLog query() + * @method static Builder|ActivityLog whereActorId($value) + * @method static Builder|ActivityLog whereActorType($value) + * @method static Builder|ActivityLog whereApiKeyId($value) + * @method static Builder|ActivityLog whereBatch($value) + * @method static Builder|ActivityLog whereDescription($value) + * @method static Builder|ActivityLog whereEvent($value) + * @method static Builder|ActivityLog whereId($value) + * @method static Builder|ActivityLog whereIp($value) + * @method static Builder|ActivityLog whereProperties($value) + * @method static Builder|ActivityLog whereTimestamp($value) + * + * @mixin \Eloquent + */ +#[Attributes\Identifiable('actl')] +class ActivityLog extends Model implements Identifiable +{ + use MassPrunable; + use HasRealtimeIdentifier; + + public const RESOURCE_NAME = 'activity_log'; + + /** + * Tracks all the events we no longer wish to display to users. These are either legacy + * events or just events where we never ended up using the associated data. + */ + public const DISABLED_EVENTS = ['server:file.upload']; + + public $timestamps = false; + + protected $guarded = [ + 'id', + 'timestamp', + ]; + + protected $casts = [ + 'properties' => 'collection', + 'timestamp' => 'datetime', + ]; + + protected $with = ['subjects']; + + public static array $validationRules = [ + 'event' => ['required', 'string'], + 'batch' => ['nullable', 'uuid'], + 'ip' => ['required', 'string'], + 'description' => ['nullable', 'string'], + 'properties' => ['array'], + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> + */ + public function actor(): MorphTo + { + $morph = $this->morphTo(); + if (method_exists($morph, 'withTrashed')) { // @phpstan-ignore function.alreadyNarrowedType + return $morph->withTrashed(); + } + + return $morph; + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\ActivityLogSubject, $this> + */ + public function subjects(): HasMany + { + return $this->hasMany(ActivityLogSubject::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\ApiKey, $this> + */ + public function apiKey(): HasOne + { + return $this->hasOne(ApiKey::class, 'id', 'api_key_id'); + } + + public function scopeForEvent(Builder $builder, string $action): Builder + { + return $builder->where('event', $action); + } + + /** + * Scopes a query to only return results where the actor is a given model. + */ + public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder + { + return $builder->whereMorphedTo('actor', $actor); + } + + /** + * Returns models to be pruned. + * + * @see https://laravel.com/docs/9.x/eloquent#pruning-models + */ + public function prunable() + { + if (is_null(config('activity.prune_days'))) { + throw new \LogicException('Cannot prune activity logs: no "prune_days" configuration value is set.'); + } + + return static::where('timestamp', '<=', Carbon::now()->subDays(config('activity.prune_days'))); + } + + /** + * Boots the model event listeners. This will trigger an activity log event every + * time a new model is inserted which can then be captured and worked with as needed. + */ + protected static function boot() + { + parent::boot(); + + static::created(function (self $model) { + Event::dispatch(new ActivityLogged($model)); + }); + } +} diff --git a/app/Models/ActivityLogSubject.php b/app/Models/ActivityLogSubject.php new file mode 100644 index 0000000000..9da2e4b399 --- /dev/null +++ b/app/Models/ActivityLogSubject.php @@ -0,0 +1,54 @@ + + */ + public function activityLog(): BelongsTo + { + return $this->belongsTo(ActivityLog::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> + */ + public function subject(): MorphTo + { + $morph = $this->morphTo(); + if (method_exists($morph, 'withTrashed')) { // @phpstan-ignore function.alreadyNarrowedType + return $morph->withTrashed(); + } + + return $morph; + } +} diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 9363b40634..0f9bcae1fc 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -2,7 +2,12 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** + * Pterodactyl\Models\Allocation. + * * @property int $id * @property int $node_id * @property string $ip @@ -14,11 +19,31 @@ * @property \Carbon\Carbon|null $updated_at * @property string $alias * @property bool $has_alias - * @property \Pterodactyl\Models\Server|null $server - * @property \Pterodactyl\Models\Node $node + * @property Server|null $server + * @property Node $node + * @property string $hashid + * + * @method static \Database\Factories\AllocationFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation query() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereIp($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereIpAlias($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereNodeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereNotes($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation wherePort($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereServerId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereUpdatedAt($value) + * + * @mixin \Eloquent */ class Allocation extends Model { + /** @use HasFactory<\Database\Factories\AllocationFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -27,22 +52,16 @@ class Allocation extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'allocations'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'node_id' => 'integer', @@ -50,10 +69,7 @@ class Allocation extends Model 'server_id' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'node_id' => 'required|exists:nodes,id', 'ip' => 'required|ip', 'port' => 'required|numeric|between:1024,65535', @@ -62,46 +78,46 @@ class Allocation extends Model 'notes' => 'nullable|string|max:256', ]; + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Return a hashid encoded string to represent the ID of the allocation. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return app()->make('hashids')->encode($this->id); } /** * Accessor to automatically provide the IP alias if defined. - * - * @param string|null $value - * - * @return string */ - public function getAliasAttribute($value) + public function getAliasAttribute(?string $value): string { return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; } /** * Accessor to quickly determine if this allocation has an alias. - * - * @param string|null $value - * - * @return bool */ - public function getHasAliasAttribute($value) + public function getHasAliasAttribute(?string $value): bool { return !is_null($this->ip_alias); } + public function toString(): string + { + return sprintf('%s:%s', $this->ip, $this->port); + } + /** * Gets information for the server associated with this allocation. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -109,9 +125,9 @@ public function server() /** * Return the Node model associated with this allocation. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Node, $this> */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 4c23633331..45ea6a1bee 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -2,42 +2,90 @@ namespace Pterodactyl\Models; +use Illuminate\Support\Str; +use Webmozart\Assert\Assert; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Laravel\Sanctum\Contracts\HasAbilities; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; /** + * Pterodactyl\Models\ApiKey. + * * @property int $id * @property int $user_id * @property int $key_type * @property string $identifier * @property string $token - * @property array $allowed_ips - * @property string $memo - * @property \Carbon\Carbon|null $last_used_at - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at + * @property array|null $allowed_ips + * @property string|null $memo + * @property \Illuminate\Support\Carbon|null $last_used_at + * @property \Illuminate\Support\Carbon|null $expires_at + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property int $r_servers + * @property int $r_nodes + * @property int $r_allocations + * @property int $r_users + * @property int $r_locations + * @property int $r_nests + * @property int $r_eggs + * @property int $r_database_hosts + * @property int $r_server_databases + * @property User $tokenable + * @property User $user + * + * @method static \Database\Factories\ApiKeyFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey query() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereAllowedIps($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereIdentifier($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereKeyType($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereLastUsedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereMemo($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRAllocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRDatabaseHosts($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereREggs($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRLocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNests($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNodes($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServerDatabases($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRUsers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value) + * + * @mixin \Eloquent */ -class ApiKey extends Model +class ApiKey extends Model implements HasAbilities { + /** @use HasFactory<\Database\Factories\ApiKeyFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'api_key'; - /** * Different API keys that can exist on the system. */ public const TYPE_NONE = 0; public const TYPE_ACCOUNT = 1; + /* @deprecated */ public const TYPE_APPLICATION = 2; + /* @deprecated */ public const TYPE_DAEMON_USER = 3; + /* @deprecated */ public const TYPE_DAEMON_APPLICATION = 4; - /** * The length of API key identifiers. */ public const IDENTIFIER_LENGTH = 16; - /** * The length of the actual API key that is encrypted and stored * in the database. @@ -46,19 +94,19 @@ class ApiKey extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'api_keys'; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'allowed_ips' => 'array', 'user_id' => 'int', + 'last_used_at' => 'datetime', + 'expires_at' => 'datetime', + self::CREATED_AT => 'datetime', + self::UPDATED_AT => 'datetime', 'r_' . AdminAcl::RESOURCE_USERS => 'int', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int', @@ -72,8 +120,6 @@ class ApiKey extends Model /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'identifier', @@ -81,22 +127,19 @@ class ApiKey extends Model 'allowed_ips', 'memo', 'last_used_at', + 'expires_at', ]; /** * Fields that should not be included when calling toArray() or toJson() * on this model. - * - * @var array */ protected $hidden = ['token']; /** * Rules to protect against invalid data entry to DB. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'user_id' => 'required|exists:users,id', 'key_type' => 'present|integer|min:0|max:4', 'identifier' => 'required|string|size:16|unique:api_keys,identifier', @@ -105,6 +148,7 @@ class ApiKey extends Model 'allowed_ips' => 'nullable|array', 'allowed_ips.*' => 'string', 'last_used_at' => 'nullable|date', + 'expires_at' => 'nullable|date', 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', @@ -116,12 +160,72 @@ class ApiKey extends Model 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', ]; + public function can($ability) + { + // todo: this was never initially implemented and only became obvious once + // internal tooling was updated and started catching this mistake. + return false; + } + + public function cant($ability) + { + return ! $this->can($ability); + } + /** - * @var array + * Returns the user this token is assigned to. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> */ - protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, - 'last_used_at', - ]; + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Required for support with Laravel Sanctum. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> + * + * @see \Laravel\Sanctum\Guard::supportsTokens() + */ + public function tokenable(): BelongsTo + { + return $this->user(); + } + + /** + * Finds the model matching the provided token. + */ + public static function findToken(string $token): ?self + { + $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); + + $model = static::where('identifier', $identifier)->first(); + if (!is_null($model) && decrypt($model->token) === substr($token, strlen($identifier))) { + return $model; + } + + return null; + } + + /** + * Returns the standard prefix for API keys in the system. + */ + public static function getPrefixForType(int $type): string + { + Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); + + return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_'; + } + + /** + * Generates a new identifier for an API key. + */ + public static function generateTokenIdentifier(int $type): string + { + $prefix = self::getPrefixForType($type); + + return $prefix . Str::random(self::IDENTIFIER_LENGTH - strlen($prefix)); + } } diff --git a/app/Models/Attributes/Identifiable.php b/app/Models/Attributes/Identifiable.php new file mode 100644 index 0000000000..9c73695c3c --- /dev/null +++ b/app/Models/Attributes/Identifiable.php @@ -0,0 +1,11 @@ + 'required|uuid', 'action' => 'required|string|max:191', 'subaction' => 'nullable|string|max:191', @@ -55,45 +24,33 @@ class AuditLog extends Model 'metadata' => 'array', ]; - /** - * @var string - */ protected $table = 'audit_logs'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var string[] - */ protected $casts = [ 'is_system' => 'bool', 'device' => 'array', 'metadata' => 'array', ]; - /** - * @var string[] - */ protected $guarded = [ 'id', 'created_at', ]; /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -103,13 +60,12 @@ public function server() * currently authenticated user if available. This model is not saved at this point, so * you can always make modifications to it as needed before saving. * - * @return $this + * @deprecated */ - public static function instance(string $action, array $metadata, bool $isSystem = false) + public static function instance(string $action, array $metadata, bool $isSystem = false): self { - /** @var \Illuminate\Http\Request $request */ $request = Container::getInstance()->make('request'); - if ($isSystem || !$request instanceof Request) { + if ($isSystem || !$request instanceof Request) { // @phpstan-ignore instanceof.alwaysTrue $request = null; } diff --git a/app/Models/Backup.php b/app/Models/Backup.php index f608edc7ae..dd9cacf858 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -3,6 +3,10 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\SoftDeletes; +use Pterodactyl\Contracts\Models\Identifiable; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; /** * @property int $id @@ -20,49 +24,35 @@ * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at * @property \Carbon\CarbonImmutable|null $deleted_at - * @property \Pterodactyl\Models\Server $server + * @property Server $server * @property \Pterodactyl\Models\AuditLog[] $audits */ -class Backup extends Model +#[Attributes\Identifiable('bkup')] +class Backup extends Model implements Identifiable { + /** @use HasFactory<\Database\Factories\BackupFactory> */ + use HasFactory; use SoftDeletes; + use HasRealtimeIdentifier; public const RESOURCE_NAME = 'backup'; public const ADAPTER_WINGS = 'wings'; public const ADAPTER_AWS_S3 = 's3'; - /** - * @var string - */ protected $table = 'backups'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var array - */ protected $casts = [ 'id' => 'int', 'is_successful' => 'bool', 'is_locked' => 'bool', 'ignored_files' => 'array', 'bytes' => 'int', + 'completed_at' => 'datetime', ]; - /** - * @var array - */ - protected $dates = [ - 'completed_at', - ]; - - /** - * @var array - */ protected $attributes = [ 'is_successful' => false, 'is_locked' => false, @@ -71,15 +61,9 @@ class Backup extends Model 'upload_id' => null, ]; - /** - * @var string[] - */ protected $guarded = ['id', 'created_at', 'updated_at', 'deleted_at']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'bail|required|numeric|exists:servers,id', 'uuid' => 'required|uuid', 'is_successful' => 'boolean', @@ -93,20 +77,10 @@ class Backup extends Model ]; /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function audits() - { - return $this->hasMany(AuditLog::class, 'metadata->backup_uuid', 'uuid') - ->where('action', 'LIKE', 'server:backup.%'); - // ->where('metadata->backup_uuid', $this->uuid); - } } diff --git a/app/Models/Database.php b/app/Models/Database.php index b09545da80..fa6d911128 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,6 +2,11 @@ namespace Pterodactyl\Models; +use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + /** * @property int $id * @property int $server_id @@ -13,11 +18,14 @@ * @property int $max_connections * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\Server $server - * @property \Pterodactyl\Models\DatabaseHost $host + * @property Server $server + * @property DatabaseHost $host */ class Database extends Model { + /** @use HasFactory<\Database\Factories\DatabaseFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -26,22 +34,16 @@ class Database extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'databases'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', 'max_connections', @@ -49,8 +51,6 @@ class Database extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'server_id' => 'integer', @@ -58,25 +58,46 @@ class Database extends Model 'max_connections' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'database_host_id' => 'required|exists:database_hosts,id', 'database' => 'required|string|alpha_dash|between:3,48', 'username' => 'string|alpha_dash|between:3,100', 'max_connections' => 'nullable|integer', - 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + 'remote' => 'required|string|regex:/^[\w\-\/.%:]+$/', 'password' => 'string', ]; + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + + /** + * Resolves the database using the ID by checking if the value provided is a HashID + * string value, or just the ID to the database itself. + * + * @param string|null $field + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function resolveRouteBinding($value, $field = null): ?\Illuminate\Database\Eloquent\Model + { + if (is_scalar($value) && ($field ?? $this->getRouteKeyName()) === 'id') { + $value = ctype_digit((string) $value) + ? $value + : Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); + } + + return $this->where($field ?? $this->getRouteKeyName(), $value)->firstOrFail(); + } + /** * Gets the host database server associated with a database. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\DatabaseHost, $this> */ - public function host() + public function host(): BelongsTo { return $this->belongsTo(DatabaseHost::class, 'database_host_id'); } @@ -84,9 +105,9 @@ public function host() /** * Gets the server associated with a database. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index eda5f84b83..ad90f11033 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property string $name @@ -16,35 +20,29 @@ */ class DatabaseHost extends Model { + /** @use HasFactory<\Database\Factories\DatabaseHostFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'database_host'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; /** * The table associated with the model. - * - * @var string */ protected $table = 'database_hosts'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', @@ -52,8 +50,6 @@ class DatabaseHost extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', @@ -63,12 +59,10 @@ class DatabaseHost extends Model /** * Validation rules to assign to this model. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|string|max:191', - 'host' => 'required|string', + 'host' => 'required|string|regex:/^[\w\-\.]+$/', 'port' => 'required|numeric|between:1,65535', 'username' => 'required|string|max:32', 'password' => 'nullable|string', @@ -78,9 +72,9 @@ class DatabaseHost extends Model /** * Gets the node associated with a database host. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Node, $this> */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } @@ -88,9 +82,9 @@ public function node() /** * Gets the databases associated with this host. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Database, $this> */ - public function databases() + public function databases(): HasMany { return $this->hasMany(Database::class); } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 6e2c26802f..7b5f6ac8c0 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -2,6 +2,12 @@ namespace Pterodactyl\Models; +use Pterodactyl\Contracts\Models\Identifiable; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property string $uuid @@ -11,8 +17,9 @@ * @property string|null $description * @property array|null $features * @property string $docker_image -- deprecated, use $docker_images + * @property array $docker_images * @property string $update_url - * @property array $docker_images + * @property bool $force_outgoing_ip * @property array|null $file_denylist * @property string|null $config_files * @property string|null $config_startup @@ -36,20 +43,30 @@ * @property string|null $inherit_config_stop * @property string $inherit_file_denylist * @property array|null $inherit_features - * @property \Pterodactyl\Models\Nest $nest + * @property Nest $nest * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables - * @property \Pterodactyl\Models\Egg|null $scriptFrom - * @property \Pterodactyl\Models\Egg|null $configFrom + * @property Egg|null $scriptFrom + * @property Egg|null $configFrom */ -class Egg extends Model +#[Attributes\Identifiable('eegg')] +class Egg extends Model implements Identifiable { + /** @use HasFactory<\Database\Factories\EggFactory> */ + use HasFactory; + use HasRealtimeIdentifier; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'egg'; + /** + * Defines the current egg export version. + */ + public const EXPORT_VERSION = 'PTDL_v2'; + /** * Different features that can be enabled on any given egg. These are used internally * to determine which types of frontend functionality should be shown to the user. Eggs @@ -64,21 +81,18 @@ class Egg extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'eggs'; /** * Fields that are not mass assignable. - * - * @var array */ protected $fillable = [ 'name', 'description', 'features', 'docker_images', + 'force_outgoing_ip', 'file_denylist', 'config_files', 'config_startup', @@ -95,23 +109,19 @@ class Egg extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'nest_id' => 'integer', 'config_from' => 'integer', 'script_is_privileged' => 'boolean', + 'force_outgoing_ip' => 'boolean', 'copy_script_from' => 'integer', 'features' => 'array', 'docker_images' => 'array', 'file_denylist' => 'array', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'nest_id' => 'required|bail|numeric|exists:nests,id', 'uuid' => 'required|string|size:36', 'name' => 'required|string|max:191', @@ -121,7 +131,7 @@ class Egg extends Model 'file_denylist' => 'array|nullable', 'file_denylist.*' => 'string', 'docker_images' => 'required|array|min:1', - 'docker_images.*' => 'required|string', + 'docker_images.*' => ['required', 'string', 'max:191', 'regex:/^[\w#\.\/\- ]*\|?~?[\w\.\/\-:@ ]*$/'], 'startup' => 'required|nullable|string', 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', @@ -129,11 +139,9 @@ class Egg extends Model 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', 'update_url' => 'sometimes|nullable|string', + 'force_outgoing_ip' => 'sometimes|boolean', ]; - /** - * @var array - */ protected $attributes = [ 'features' => null, 'file_denylist' => null, @@ -147,10 +155,8 @@ class Egg extends Model /** * Returns the install script for the egg; if egg is copying from another * it will return the copied script. - * - * @return string */ - public function getCopyScriptInstallAttribute() + public function getCopyScriptInstallAttribute(): ?string { if (!is_null($this->script_install) || is_null($this->copy_script_from)) { return $this->script_install; @@ -162,12 +168,10 @@ public function getCopyScriptInstallAttribute() /** * Returns the entry command for the egg; if egg is copying from another * it will return the copied entry command. - * - * @return string */ - public function getCopyScriptEntryAttribute() + public function getCopyScriptEntryAttribute(): string { - if (!is_null($this->script_entry) || is_null($this->copy_script_from)) { + if (is_null($this->copy_script_from)) { return $this->script_entry; } @@ -177,12 +181,10 @@ public function getCopyScriptEntryAttribute() /** * Returns the install container for the egg; if egg is copying from another * it will return the copied install container. - * - * @return string */ - public function getCopyScriptContainerAttribute() + public function getCopyScriptContainerAttribute(): string { - if (!is_null($this->script_container) || is_null($this->copy_script_from)) { + if (is_null($this->copy_script_from)) { return $this->script_container; } @@ -191,10 +193,8 @@ public function getCopyScriptContainerAttribute() /** * Return the file configuration for an egg. - * - * @return string */ - public function getInheritConfigFilesAttribute() + public function getInheritConfigFilesAttribute(): ?string { if (!is_null($this->config_files) || is_null($this->config_from)) { return $this->config_files; @@ -205,10 +205,8 @@ public function getInheritConfigFilesAttribute() /** * Return the startup configuration for an egg. - * - * @return string */ - public function getInheritConfigStartupAttribute() + public function getInheritConfigStartupAttribute(): ?string { if (!is_null($this->config_startup) || is_null($this->config_from)) { return $this->config_startup; @@ -219,10 +217,8 @@ public function getInheritConfigStartupAttribute() /** * Return the log reading configuration for an egg. - * - * @return string */ - public function getInheritConfigLogsAttribute() + public function getInheritConfigLogsAttribute(): ?string { if (!is_null($this->config_logs) || is_null($this->config_from)) { return $this->config_logs; @@ -233,10 +229,8 @@ public function getInheritConfigLogsAttribute() /** * Return the stop command configuration for an egg. - * - * @return string */ - public function getInheritConfigStopAttribute() + public function getInheritConfigStopAttribute(): ?string { if (!is_null($this->config_stop) || is_null($this->config_from)) { return $this->config_stop; @@ -248,10 +242,8 @@ public function getInheritConfigStopAttribute() /** * Returns the features available to this egg from the parent configuration if there are * no features defined for this egg specifically and there is a parent egg configured. - * - * @return array|null */ - public function getInheritFeaturesAttribute() + public function getInheritFeaturesAttribute(): ?array { if (!is_null($this->features) || is_null($this->config_from)) { return $this->features; @@ -263,10 +255,8 @@ public function getInheritFeaturesAttribute() /** * Returns the features available to this egg from the parent configuration if there are * no features defined for this egg specifically and there is a parent egg configured. - * - * @return string[]|null */ - public function getInheritFileDenylistAttribute() + public function getInheritFileDenylistAttribute(): ?array { if (is_null($this->config_from)) { return $this->file_denylist; @@ -278,9 +268,9 @@ public function getInheritFileDenylistAttribute() /** * Gets nest associated with an egg. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Nest, $this> */ - public function nest() + public function nest(): BelongsTo { return $this->belongsTo(Nest::class); } @@ -288,9 +278,9 @@ public function nest() /** * Gets all servers associated with this egg. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Server, $this> */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class, 'egg_id'); } @@ -298,9 +288,9 @@ public function servers() /** * Gets all variables associated with this egg. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\EggVariable, $this> */ - public function variables() + public function variables(): HasMany { return $this->hasMany(EggVariable::class, 'egg_id'); } @@ -308,9 +298,9 @@ public function variables() /** * Get the parent egg from which to copy scripts. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function scriptFrom() + public function scriptFrom(): BelongsTo { return $this->belongsTo(self::class, 'copy_script_from'); } @@ -318,9 +308,9 @@ public function scriptFrom() /** * Get the parent egg from which to copy configuration settings. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function configFrom() + public function configFrom(): BelongsTo { return $this->belongsTo(self::class, 'config_from'); } diff --git a/app/Models/EggMount.php b/app/Models/EggMount.php index cd85673ce4..2016f54236 100644 --- a/app/Models/EggMount.php +++ b/app/Models/EggMount.php @@ -4,18 +4,9 @@ class EggMount extends Model { - /** - * @var string - */ protected $table = 'egg_mount'; - /** - * @var null - */ - protected $primaryKey = null; + protected $primaryKey; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 99966da3a3..f30bbc8760 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property int $egg_id @@ -15,8 +19,8 @@ * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at * @property bool $required - * @property \Pterodactyl\Models\Egg $egg - * @property \Pterodactyl\Models\ServerVariable $serverVariable + * @property Egg $egg + * @property ServerVariable $serverVariable * * The "server_value" variable is only present on the object if you've loaded this model * using the server relationship. @@ -24,6 +28,9 @@ */ class EggVariable extends Model { + /** @use HasFactory<\Database\Factories\EggVariableFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -32,34 +39,23 @@ class EggVariable extends Model /** * Reserved environment variable names. - * - * @var string */ public const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; /** * The table associated with the model. - * - * @var string */ protected $table = 'egg_variables'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'egg_id' => 'integer', @@ -67,10 +63,7 @@ class EggVariable extends Model 'user_editable' => 'bool', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'egg_id' => 'exists:eggs,id', 'name' => 'required|string|between:1,191', 'description' => 'string', @@ -81,26 +74,20 @@ class EggVariable extends Model 'rules' => 'required|string', ]; - /** - * @var array - */ protected $attributes = [ 'user_editable' => 0, 'user_viewable' => 0, ]; - /** - * @return bool - */ - public function getRequiredAttribute() + public function getRequiredAttribute(): bool { return in_array('required', explode('|', $this->rules)); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Egg, $this> */ - public function egg() + public function egg(): HasOne { return $this->hasOne(Egg::class); } @@ -108,9 +95,9 @@ public function egg() /** * Return server variables associated with this variable. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\ServerVariable, $this> */ - public function serverVariable() + public function serverVariable(): HasMany { return $this->hasMany(ServerVariable::class, 'variable_id'); } diff --git a/app/Models/Filters/AdminServerFilter.php b/app/Models/Filters/AdminServerFilter.php index 32580ea51a..638946734e 100644 --- a/app/Models/Filters/AdminServerFilter.php +++ b/app/Models/Filters/AdminServerFilter.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Models\Filters; -use BadMethodCallException; use Spatie\QueryBuilder\Filters\Filter; use Illuminate\Database\Eloquent\Builder; @@ -17,7 +16,7 @@ class AdminServerFilter implements Filter public function __invoke(Builder $query, $value, string $property) { if ($query->getQuery()->from !== 'servers') { - throw new BadMethodCallException('Cannot use the AdminServerFilter against a non-server model.'); + throw new \BadMethodCallException('Cannot use the AdminServerFilter against a non-server model.'); } $query ->select('servers.*') diff --git a/app/Models/Filters/MultiFieldServerFilter.php b/app/Models/Filters/MultiFieldServerFilter.php index 2aac64e810..fe0a7ebd5d 100644 --- a/app/Models/Filters/MultiFieldServerFilter.php +++ b/app/Models/Filters/MultiFieldServerFilter.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Models\Filters; -use BadMethodCallException; use Illuminate\Support\Str; use Spatie\QueryBuilder\Filters\Filter; use Illuminate\Database\Eloquent\Builder; @@ -25,7 +24,7 @@ class MultiFieldServerFilter implements Filter public function __invoke(Builder $query, $value, string $property) { if ($query->getQuery()->from !== 'servers') { - throw new BadMethodCallException('Cannot use the MultiFieldServerFilter against a non-server model.'); + throw new \BadMethodCallException('Cannot use the MultiFieldServerFilter against a non-server model.'); } if (preg_match(self::IPV4_REGEX, $value) || preg_match('/^:\d{1,5}$/', $value)) { diff --git a/app/Models/Location.php b/app/Models/Location.php index bc9a1fb4fd..b083d7b2b4 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; + /** * @property int $id * @property string $short @@ -13,6 +17,9 @@ */ class Location extends Model { + /** @use HasFactory<\Database\Factories\LocationFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -21,34 +28,33 @@ class Location extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'locations'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Rules ensuring that the raw data stored in the database meets expectations. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'short' => 'required|string|between:1,60|unique:locations,short', 'long' => 'string|nullable|between:1,191', ]; + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Gets the nodes in a specified location. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Node, $this> */ - public function nodes() + public function nodes(): HasMany { return $this->hasMany(Node::class); } @@ -56,9 +62,9 @@ public function nodes() /** * Gets the servers within a given location. * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Pterodactyl\Models\Server, \Pterodactyl\Models\Node, $this> */ - public function servers() + public function servers(): HasManyThrough { return $this->hasManyThrough(Server::class, Node::class); } diff --git a/app/Models/Model.php b/app/Models/Model.php index e5e7fb976b..01958c0bc1 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -2,49 +2,34 @@ namespace Pterodactyl\Models; +use Carbon\CarbonImmutable; +use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Illuminate\Support\Carbon; use Illuminate\Validation\Rule; use Illuminate\Container\Container; -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Contracts\Validation\Validator; +use Illuminate\Validation\ValidationException; use Pterodactyl\Exceptions\Model\DataValidationException; use Illuminate\Database\Eloquent\Model as IlluminateModel; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; abstract class Model extends IlluminateModel { - use HasFactory; - /** * Set to true to return immutable Carbon date instances from the model. - * - * @var bool */ - protected $immutableDates = false; + protected bool $immutableDates = false; /** * Determines if the model should undergo data validation before it is saved * to the database. - * - * @var bool */ - protected $skipValidation = false; + protected bool $skipValidation = false; - /** - * The validator instance used by this model. - * - * @var \Illuminate\Validation\Validator - */ - protected $validator; + protected static ValidationFactory $validatorFactory; - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - protected static $validatorFactory; - - /** - * @var array - */ - public static $validationRules = []; + public static array $validationRules = []; /** * Listen for the model saving event and fire off the validation @@ -56,11 +41,13 @@ protected static function boot() { parent::boot(); - static::$validatorFactory = Container::getInstance()->make(Factory::class); + static::$validatorFactory = Container::getInstance()->make(ValidationFactory::class); static::saving(function (Model $model) { - if (!$model->validate()) { - throw new DataValidationException($model->getValidator()); + try { + $model->validate(); + } catch (ValidationException $exception) { + throw new DataValidationException($exception->validator, $model); } return true; @@ -68,11 +55,23 @@ protected static function boot() } /** - * Set the model to skip validation when saving. + * Returns the model key to use for route model binding. By default, we'll + * assume every model uses a UUID field for this. If the model does not have + * a UUID and is using a different key it should be specified on the model + * itself. * - * @return $this + * You may also optionally override this on a per-route basis by declaring + * the key name in the URL definition, like "{user:id}". + */ + public function getRouteKeyName(): string + { + return 'uuid'; + } + + /** + * Set the model to skip validation when saving. */ - public function skipValidation() + public function skipValidation(): self { $this->skipValidation = true; @@ -81,27 +80,19 @@ public function skipValidation() /** * Returns the validator instance used by this model. - * - * @return \Illuminate\Validation\Validator|\Illuminate\Contracts\Validation\Validator */ - public function getValidator() + public function getValidator(): \Illuminate\Validation\Validator { - $rules = $this->getKey() ? static::getRulesForUpdate($this) : static::getRules(); + $rules = $this->exists ? static::getRulesForUpdate($this) : static::getRules(); - return $this->validator ?: $this->validator = static::$validatorFactory->make( - [], - $rules, - [], - [] - ); + // @phpstan-ignore-next-line return.type + return static::$validatorFactory->make([], $rules); } /** * Returns the rules associated with this model. - * - * @return array */ - public static function getRules() + public static function getRules(): array { $rules = static::$validationRules; foreach ($rules as $key => &$rule) { @@ -111,25 +102,30 @@ public static function getRules() return $rules; } + /** + * Returns the rules for a specific field. If the field is not found an empty + * array is returned. + */ + public static function getRulesForField(string $field): array + { + return Arr::get(static::getRules(), $field) ?? []; + } + /** * Returns the rules associated with the model, specifically for updating the given model * rather than just creating it. - * - * @param \Illuminate\Database\Eloquent\Model|int|string $id - * - * @return array */ - public static function getRulesForUpdate($id, string $primaryKey = 'id') + public static function getRulesForUpdate($model, string $column = 'id'): array { - if ($id instanceof Model) { - [$primaryKey, $id] = [$id->getKeyName(), $id->getKey()]; + if ($model instanceof Model) { + [$id, $column] = [$model->getKey(), $model->getKeyName()]; } $rules = static::getRules(); foreach ($rules as $key => &$data) { // For each rule in a given field, iterate over it and confirm if the rule // is one for a unique field. If that is the case, append the ID of the current - // working model so we don't run into errors due to the way that field validation + // working model, so we don't run into errors due to the way that field validation // works. foreach ($data as &$datum) { if (!is_string($datum) || !Str::startsWith($datum, 'unique')) { @@ -139,7 +135,7 @@ public static function getRulesForUpdate($id, string $primaryKey = 'id') [, $args] = explode(':', $datum); $args = explode(',', $args); - $datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id, $primaryKey)->__toString(); + $datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id ?? $model, $column); } } @@ -149,33 +145,34 @@ public static function getRulesForUpdate($id, string $primaryKey = 'id') /** * Determines if the model is in a valid state or not. * - * @return bool + * @throws ValidationException */ - public function validate() + public function validate(): void { if ($this->skipValidation) { - return true; + return; } - return $this->getValidator()->setData( - // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist - // for that model. Doing this will return all of the attributes in a format that can - // properly be validated. + $validator = $this->getValidator(); + $validator->setData( + // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist + // for that model. Doing this will return all the attributes in a format that can + // properly be validated. $this->addCastAttributesToArray( $this->getAttributes(), $this->getMutatedAttributes() ) - )->passes(); + ); + + if (!$validator->passes()) { + throw new ValidationException($validator); + } } /** * Return a timestamp as DateTime object. - * - * @param mixed $value - * - * @return \Illuminate\Support\Carbon|\Carbon\CarbonImmutable */ - protected function asDateTime($value) + protected function asDateTime($value): Carbon|CarbonImmutable { if (!$this->immutableDates) { return parent::asDateTime($value); diff --git a/app/Models/Mount.php b/app/Models/Mount.php index c1eb7a3a1a..7d036a4aaa 100644 --- a/app/Models/Mount.php +++ b/app/Models/Mount.php @@ -3,6 +3,9 @@ namespace Pterodactyl\Models; use Illuminate\Validation\Rules\NotIn; +use Pterodactyl\Contracts\Models\Identifiable; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id @@ -17,8 +20,11 @@ * @property \Pterodactyl\Models\Node[]|\Illuminate\Database\Eloquent\Collection $nodes * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers */ -class Mount extends Model +#[Attributes\Identifiable('moun')] +class Mount extends Model implements Identifiable { + use HasRealtimeIdentifier; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -27,22 +33,16 @@ class Mount extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'mounts'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'uuid']; /** * Default values for specific fields in the database. - * - * @var array */ protected $casts = [ 'id' => 'int', @@ -52,10 +52,8 @@ class Mount extends Model /** * Rules verifying that the data being stored matches the expectations of the database. - * - * @var string */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|string|min:2|max:64|unique:mounts,name', 'description' => 'nullable|string|max:191', 'source' => 'required|string', @@ -68,7 +66,7 @@ class Mount extends Model * Implement language verification by overriding Eloquence's gather * rules function. */ - public static function getRules() + public static function getRules(): array { $rules = parent::getRules(); @@ -80,15 +78,11 @@ public static function getRules() /** * Disable timestamps on this model. - * - * @var bool */ public $timestamps = false; /** * Blacklisted source paths. - * - * @var string[] */ public static $invalidSourcePaths = [ '/etc/pterodactyl', @@ -98,8 +92,6 @@ public static function getRules() /** * Blacklisted target paths. - * - * @var string[] */ public static $invalidTargetPaths = [ '/home/container', @@ -108,9 +100,9 @@ public static function getRules() /** * Returns all eggs that have this mount assigned. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Pterodactyl\Models\Egg, $this> */ - public function eggs() + public function eggs(): BelongsToMany { return $this->belongsToMany(Egg::class); } @@ -118,9 +110,9 @@ public function eggs() /** * Returns all nodes that have this mount assigned. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Pterodactyl\Models\Node, $this> */ - public function nodes() + public function nodes(): BelongsToMany { return $this->belongsToMany(Node::class); } @@ -128,9 +120,9 @@ public function nodes() /** * Returns all servers that have this mount assigned. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Pterodactyl\Models\Server, $this> */ - public function servers() + public function servers(): BelongsToMany { return $this->belongsToMany(Server::class); } diff --git a/app/Models/MountNode.php b/app/Models/MountNode.php index a897dd6dd5..dae6b3179e 100644 --- a/app/Models/MountNode.php +++ b/app/Models/MountNode.php @@ -6,18 +6,9 @@ class MountNode extends Model { - /** - * @var string - */ protected $table = 'mount_node'; - /** - * @var null - */ - protected $primaryKey = null; + protected $primaryKey; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/MountServer.php b/app/Models/MountServer.php index 21bf8fe2db..61d9bcb658 100644 --- a/app/Models/MountServer.php +++ b/app/Models/MountServer.php @@ -6,23 +6,11 @@ class MountServer extends Model { - /** - * @var string - */ protected $table = 'mount_server'; - /** - * @var bool - */ public $timestamps = false; - /** - * @var null - */ - protected $primaryKey = null; + protected $primaryKey; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/Nest.php b/app/Models/Nest.php index 1658171596..e38b0e63cb 100644 --- a/app/Models/Nest.php +++ b/app/Models/Nest.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property string $uuid @@ -15,6 +18,9 @@ */ class Nest extends Model { + /** @use HasFactory<\Database\Factories\NestFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -23,25 +29,18 @@ class Nest extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'nests'; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', 'description', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'author' => 'required|string|email', 'name' => 'required|string|max:191', 'description' => 'nullable|string', @@ -50,9 +49,9 @@ class Nest extends Model /** * Gets all eggs associated with this service. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Egg, $this> */ - public function eggs() + public function eggs(): HasMany { return $this->hasMany(Egg::class); } @@ -60,9 +59,9 @@ public function eggs() /** * Gets all servers associated with this nest. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Server, $this> */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class); } diff --git a/app/Models/Node.php b/app/Models/Node.php index bb77834663..22c962aa93 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -7,6 +7,12 @@ use Illuminate\Container\Container; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Contracts\Models\Identifiable; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; /** * @property int $id @@ -31,14 +37,18 @@ * @property string $daemonBase * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\Location $location + * @property Location $location * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations */ -class Node extends Model +#[Attributes\Identifiable('node')] +class Node extends Model implements Identifiable { + /** @use HasFactory<\Database\Factories\NodeFactory> */ + use HasFactory; use Notifiable; + use HasRealtimeIdentifier; /** * The resource name for this model when it is transformed into an @@ -51,22 +61,16 @@ class Node extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'nodes'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['daemon_token_id', 'daemon_token']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'location_id' => 'integer', @@ -81,22 +85,17 @@ class Node extends Model /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'public', 'name', 'location_id', - 'fqdn', 'scheme', 'behind_proxy', + 'description', 'fqdn', 'scheme', 'behind_proxy', 'memory', 'memory_overallocate', 'disk', 'disk_overallocate', 'upload_size', 'daemonBase', 'daemonSFTP', 'daemonListen', 'description', 'maintenance_mode', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|regex:/^([\w .-]{1,100})$/', 'description' => 'string|nullable', 'location_id' => 'required|exists:locations,id', @@ -112,13 +111,11 @@ class Node extends Model 'daemonSFTP' => 'required|numeric|between:1,65535', 'daemonListen' => 'required|numeric|between:1,65535', 'maintenance_mode' => 'boolean', - 'upload_size' => 'int|between:1,1024', + 'upload_size' => 'int|min:1', ]; /** * Default values for specific columns that are generally not changed on base installs. - * - * @var array */ protected $attributes = [ 'public' => true, @@ -141,10 +138,8 @@ public function getConnectionAddress(): string /** * Returns the configuration as an array. - * - * @return array */ - public function getConfiguration() + public function getConfiguration(): array { return [ 'debug' => false, @@ -174,20 +169,16 @@ public function getConfiguration() /** * Returns the configuration in Yaml format. - * - * @return string */ - public function getYamlConfiguration() + public function getYamlConfiguration(): string { return Yaml::dump($this->getConfiguration(), 4, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); } /** * Returns the configuration in JSON format. - * - * @return string */ - public function getJsonConfiguration(bool $pretty = false) + public function getJsonConfiguration(bool $pretty = false): string { return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); } @@ -202,10 +193,15 @@ public function getDecryptedKey(): string ); } + public function isUnderMaintenance(): bool + { + return $this->maintenance_mode; + } + /** - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Pterodactyl\Models\Mount, \Pterodactyl\Models\MountNode, $this> */ - public function mounts() + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); } @@ -213,9 +209,9 @@ public function mounts() /** * Gets the location associated with a node. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Location, $this> */ - public function location() + public function location(): BelongsTo { return $this->belongsTo(Location::class); } @@ -223,9 +219,9 @@ public function location() /** * Gets the servers associated with a node. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Server, $this> */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class); } @@ -233,9 +229,9 @@ public function servers() /** * Gets the allocations associated with a node. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Allocation, $this> */ - public function allocations() + public function allocations(): HasMany { return $this->hasMany(Allocation::class); } @@ -248,6 +244,7 @@ public function isViable(int $memory, int $disk): bool $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); + // @phpstan-ignore-next-line property.notFound, property.notFound return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; } } diff --git a/app/Models/Objects/DeploymentObject.php b/app/Models/Objects/DeploymentObject.php index 3c62f0baec..0c44be08e2 100644 --- a/app/Models/Objects/DeploymentObject.php +++ b/app/Models/Objects/DeploymentObject.php @@ -4,30 +4,18 @@ class DeploymentObject { - /** - * @var bool - */ - private $dedicated = false; + private bool $dedicated = false; - /** - * @var array - */ - private $locations = []; + private array $locations = []; - /** - * @var array - */ - private $ports = []; + private array $ports = []; public function isDedicated(): bool { return $this->dedicated; } - /** - * @return $this - */ - public function setDedicated(bool $dedicated) + public function setDedicated(bool $dedicated): self { $this->dedicated = $dedicated; @@ -39,10 +27,7 @@ public function getLocations(): array return $this->locations; } - /** - * @return $this - */ - public function setLocations(array $locations) + public function setLocations(array $locations): self { $this->locations = $locations; @@ -54,10 +39,7 @@ public function getPorts(): array return $this->ports; } - /** - * @return $this - */ - public function setPorts(array $ports) + public function setPorts(array $ports): self { $this->ports = $ports; diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 5a0fc95f82..b2e00070e7 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -63,53 +63,42 @@ class Permission extends Model public const ACTION_SETTINGS_RENAME = 'settings.rename'; public const ACTION_SETTINGS_REINSTALL = 'settings.reinstall'; + public const ACTION_ACTIVITY_READ = 'activity.read'; + /** * Should timestamps be used on this model. - * - * @var bool */ public $timestamps = false; /** * The table associated with the model. - * - * @var string */ protected $table = 'permissions'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'subuser_id' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'subuser_id' => 'required|numeric|min:1', 'permission' => 'required|string', ]; /** - * All of the permissions available on the system. You should use self::permissions() + * All the permissions available on the system. You should use self::permissions() * to retrieve them, and not directly access this array as it is subject to change. * - * @var array - * * @see \Pterodactyl\Models\Permission::permissions() */ - protected static $permissions = [ + protected static array $permissions = [ 'websocket' => [ 'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.', 'keys' => [ @@ -206,17 +195,22 @@ class Permission extends Model 'settings' => [ 'description' => 'Permissions that control a user\'s access to the settings for this server.', 'keys' => [ - 'rename' => 'Allows a user to rename this server.', + 'rename' => 'Allows a user to rename this server and change the description of it.', 'reinstall' => 'Allows a user to trigger a reinstall of this server.', ], ], + + 'activity' => [ + 'description' => 'Permissions that control a user\'s access to the server activity logs.', + 'keys' => [ + 'read' => 'Allows a user to view the activity logs for the server.', + ], + ], ]; /** - * Returns all of the permissions available on the system for a user to + * Returns all the permissions available on the system for a user to * have when controlling a server. - * - * @return \Illuminate\Database\Eloquent\Collection */ public static function permissions(): Collection { diff --git a/app/Models/RecoveryToken.php b/app/Models/RecoveryToken.php index 0244c7bca4..64b3b9fadb 100644 --- a/app/Models/RecoveryToken.php +++ b/app/Models/RecoveryToken.php @@ -2,12 +2,14 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property int $user_id * @property string $token * @property \Carbon\CarbonImmutable $created_at - * @property \Pterodactyl\Models\User $user + * @property User $user */ class RecoveryToken extends Model { @@ -16,27 +18,18 @@ class RecoveryToken extends Model */ public const UPDATED_AT = null; - /** - * @var bool - */ public $timestamps = true; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var string[] - */ - public static $validationRules = [ + public static array $validationRules = [ 'token' => 'required|string', ]; /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 82a43c732c..4d988d000b 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -5,6 +5,9 @@ use Cron\CronExpression; use Carbon\CarbonImmutable; use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Pterodactyl\Contracts\Extensions\HashidsInterface; /** @@ -24,11 +27,14 @@ * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property string $hashid - * @property \Pterodactyl\Models\Server $server - * @property \Pterodactyl\Models\Task[]|\Illuminate\Support\Collection $tasks + * @property Server $server + * @property \Illuminate\Database\Eloquent\Collection $tasks */ class Schedule extends Model { + /** @use HasFactory<\Database\Factories\ScheduleFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -37,22 +43,16 @@ class Schedule extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'schedules'; /** * Always return the tasks associated with this schedule. - * - * @var array */ protected $with = ['tasks']; /** * Mass assignable attributes on this model. - * - * @var array */ protected $fillable = [ 'server_id', @@ -69,30 +69,16 @@ class Schedule extends Model 'next_run_at', ]; - /** - * @var array - */ protected $casts = [ 'id' => 'integer', 'server_id' => 'integer', 'is_active' => 'boolean', 'is_processing' => 'boolean', 'only_when_online' => 'boolean', + 'last_run_at' => 'datetime', + 'next_run_at' => 'datetime', ]; - /** - * Columns to mutate to a date. - * - * @var array - */ - protected $dates = [ - 'last_run_at', - 'next_run_at', - ]; - - /** - * @var array - */ protected $attributes = [ 'name' => null, 'cron_day_of_week' => '*', @@ -105,10 +91,7 @@ class Schedule extends Model 'only_when_online' => false, ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|exists:servers,id', 'name' => 'required|string|max:191', 'cron_day_of_week' => 'required|string', @@ -123,28 +106,27 @@ class Schedule extends Model 'next_run_at' => 'nullable|date', ]; + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Returns the schedule's execution crontab entry as a string. * - * @return \Carbon\CarbonImmutable - * * @throws \Exception */ - public function getNextRunDate() + public function getNextRunDate(): CarbonImmutable { $formatted = sprintf('%s %s %s %s %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_month, $this->cron_day_of_week); - return CarbonImmutable::createFromTimestamp( - (new CronExpression($formatted))->getNextRunDate()->getTimestamp() - ); + return CarbonImmutable::instance((new CronExpression($formatted))->getNextRunDate()); } /** * Return a hashid encoded string to represent the ID of the schedule. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } @@ -152,9 +134,9 @@ public function getHashidAttribute() /** * Return tasks belonging to a schedule. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Task, $this> */ - public function tasks() + public function tasks(): HasMany { return $this->hasMany(Task::class); } @@ -162,9 +144,9 @@ public function tasks() /** * Return the server model that a schedule belongs to. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } diff --git a/app/Models/Server.php b/app/Models/Server.php index bd539282a2..7411a2a9ce 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,13 +2,22 @@ namespace Pterodactyl\Models; -use Closure; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; +use Pterodactyl\Contracts\Models\Identifiable; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\MorphToMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** + * \Pterodactyl\Models\Server. + * * @property int $id * @property string|null $external_id * @property string $uuid @@ -24,93 +33,124 @@ * @property int $disk * @property int $io * @property int $cpu - * @property string $threads + * @property string|null $threads * @property bool $oom_disabled * @property int $allocation_id * @property int $nest_id * @property int $egg_id * @property string $startup * @property string $image - * @property int $allocation_limit - * @property int $database_limit + * @property int|null $allocation_limit + * @property int|null $database_limit * @property int $backup_limit - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\User $user - * @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers - * @property \Pterodactyl\Models\Allocation $allocation - * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations - * @property \Pterodactyl\Models\Node $node - * @property \Pterodactyl\Models\Nest $nest - * @property \Pterodactyl\Models\Egg $egg - * @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables - * @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule - * @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases - * @property \Pterodactyl\Models\Location $location - * @property \Pterodactyl\Models\ServerTransfer $transfer - * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups - * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts - * @property \Pterodactyl\Models\AuditLog[] $audits + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $installed_at + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLog[] $activity + * @property int|null $activity_count + * @property Allocation|null $allocation + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations + * @property int|null $allocations_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups + * @property int|null $backups_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases + * @property int|null $databases_count + * @property Egg|null $egg + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Mount[] $mounts + * @property int|null $mounts_count + * @property Nest $nest + * @property Node $node + * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property int|null $notifications_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Schedule[] $schedules + * @property int|null $schedules_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Subuser[] $subusers + * @property int|null $subusers_count + * @property ServerTransfer|null $transfer + * @property User $user + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables + * @property int|null $variables_count + * + * @method static \Database\Factories\ServerFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Server newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Server query() + * @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereBackupLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereCpu($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDatabaseLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDisk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereEggId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereExternalId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereImage($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereIo($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereMemory($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereNestId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereNodeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereOomDisabled($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereOwnerId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereSkipScripts($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereStartup($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereSwap($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereThreads($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value) + * + * @mixin \Eloquent */ -class Server extends Model +#[Attributes\Identifiable('serv')] +class Server extends Model implements Identifiable { + /** @use HasFactory<\Database\Factories\ServerFactory> */ + use HasFactory; use BelongsToThrough; use Notifiable; + use HasRealtimeIdentifier; /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'server'; - public const STATUS_INSTALLING = 'installing'; public const STATUS_INSTALL_FAILED = 'install_failed'; + public const STATUS_REINSTALL_FAILED = 'reinstall_failed'; public const STATUS_SUSPENDED = 'suspended'; public const STATUS_RESTORING_BACKUP = 'restoring_backup'; /** * The table associated with the model. - * - * @var string */ protected $table = 'servers'; /** * Default values when creating the model. We want to switch to disabling OOM killer * on server instances unless the user specifies otherwise in the request. - * - * @var array */ protected $attributes = [ 'status' => self::STATUS_INSTALLING, 'oom_disabled' => true, + 'installed_at' => null, ]; /** * The default relationships to load for all server models. - * - * @var string[] */ protected $with = ['allocation']; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; - /** * Fields that are not mass assignable. - * - * @var array */ - protected $guarded = ['id', self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; + protected $guarded = ['id', self::CREATED_AT, self::UPDATED_AT, 'deleted_at', 'installed_at']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers', 'owner_id' => 'required|integer|exists:users,id', 'name' => 'required|string|min:1|max:191', @@ -129,7 +169,7 @@ class Server extends Model 'egg_id' => 'required|exists:eggs,id', 'startup' => 'required|string', 'skip_scripts' => 'sometimes|boolean', - 'image' => 'required|string|max:191', + 'image' => ['required', 'string', 'max:191', 'regex:/^~?[\w\.\/\-:@ ]*$/'], 'database_limit' => 'present|nullable|integer|min:0', 'allocation_limit' => 'sometimes|nullable|integer|min:0', 'backup_limit' => 'present|nullable|integer|min:0', @@ -137,8 +177,6 @@ class Server extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'node_id' => 'integer', @@ -156,6 +194,10 @@ class Server extends Model 'database_limit' => 'integer', 'allocation_limit' => 'integer', 'backup_limit' => 'integer', + self::CREATED_AT => 'datetime', + self::UPDATED_AT => 'datetime', + 'deleted_at' => 'datetime', + 'installed_at' => 'datetime', ]; /** @@ -181,9 +223,9 @@ public function isSuspended(): bool /** * Gets the user who owns the server. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class, 'owner_id'); } @@ -191,9 +233,9 @@ public function user() /** * Gets the subusers associated with a server. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Subuser, $this> */ - public function subusers() + public function subusers(): HasMany { return $this->hasMany(Subuser::class, 'server_id', 'id'); } @@ -201,9 +243,9 @@ public function subusers() /** * Gets the default allocation for a server. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Allocation, $this> */ - public function allocation() + public function allocation(): HasOne { return $this->hasOne(Allocation::class, 'id', 'allocation_id'); } @@ -211,9 +253,9 @@ public function allocation() /** * Gets all allocations associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Allocation, $this> */ - public function allocations() + public function allocations(): HasMany { return $this->hasMany(Allocation::class, 'server_id'); } @@ -221,9 +263,9 @@ public function allocations() /** * Gets information for the nest associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Nest, $this> */ - public function nest() + public function nest(): BelongsTo { return $this->belongsTo(Nest::class); } @@ -231,9 +273,9 @@ public function nest() /** * Gets information for the egg associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Egg, $this> */ - public function egg() + public function egg(): HasOne { return $this->hasOne(Egg::class, 'id', 'egg_id'); } @@ -241,15 +283,15 @@ public function egg() /** * Gets information for the service variables associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\EggVariable, $this> */ - public function variables() + public function variables(): HasMany { return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id') ->select(['egg_variables.*', 'server_variables.variable_value as server_value']) ->leftJoin('server_variables', function (JoinClause $join) { // Don't forget to join against the server ID as well since the way we're using this relationship - // would actually return all of the variables and their values for _all_ servers using that egg,\ + // would actually return all the variables and their values for _all_ servers using that egg, // rather than only the server for this model. // // @see https://github.com/pterodactyl/panel/issues/2250 @@ -261,9 +303,9 @@ public function variables() /** * Gets information for the node associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Node, $this> */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } @@ -271,9 +313,9 @@ public function node() /** * Gets information for the tasks associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Schedule, $this> */ - public function schedule() + public function schedules(): HasMany { return $this->hasMany(Schedule::class); } @@ -281,9 +323,9 @@ public function schedule() /** * Gets all databases associated with a server. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Database, $this> */ - public function databases() + public function databases(): HasMany { return $this->hasMany(Database::class); } @@ -291,29 +333,29 @@ public function databases() /** * Returns the location that a server belongs to. * - * @return \Znck\Eloquent\Relations\BelongsToThrough + * @return \Znck\Eloquent\Relations\BelongsToThrough<\Pterodactyl\Models\Location, \Pterodactyl\Models\Node> * * @throws \Exception */ - public function location() + public function location(): \Znck\Eloquent\Relations\BelongsToThrough { - return $this->belongsToThrough(Location::class, Node::class); + return $this->belongsToThrough(Location::class, Node::class); // @phpstan-ignore return.type } /** * Returns the associated server transfer. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\ServerTransfer, $this> */ - public function transfer() + public function transfer(): HasOne { return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Backup, $this> */ - public function backups() + public function backups(): HasMany { return $this->hasMany(Backup::class); } @@ -321,72 +363,55 @@ public function backups() /** * Returns all mounts that have this server has mounted. * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Pterodactyl\Models\Mount, \Pterodactyl\Models\MountServer, $this> */ - public function mounts() + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id'); } /** - * Returns a fresh AuditLog model for the server. This model is not saved to the - * database when created, so it is up to the caller to correctly store it as needed. + * Returns all of the activity log entries where the server is the subject. * - * @return \Pterodactyl\Models\AuditLog + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\Pterodactyl\Models\ActivityLog, $this> */ - public function newAuditEvent(string $action, array $metadata = []): AuditLog + public function activity(): MorphToMany { - return AuditLog::instance($action, $metadata)->fill([ - 'server_id' => $this->id, - ]); + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } /** - * Stores a new audit event for a server by using a transaction. If the transaction - * fails for any reason everything executed within will be rolled back. The callback - * passed in will receive the AuditLog model before it is saved and the second argument - * will be the current server instance. The callback should modify the audit entry as - * needed before finishing, any changes will be persisted. - * - * The response from the callback is returned to the caller. - * - * @return mixed + * Checks if the server is currently in a user-accessible state. If not, an + * exception is raised. This should be called whenever something needs to make + * sure the server is not in a weird state that should block user access. * - * @throws \Throwable - */ - public function audit(string $action, Closure $callback) - { - return $this->getConnection()->transaction(function () use ($action, $callback) { - $model = $this->newAuditEvent($action); - $response = $callback($model, $this); - $model->save(); - - return $response; - }); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @throws ServerStateConflictException */ - public function audits() + public function validateCurrentState() { - return $this->hasMany(AuditLog::class); + if ( + $this->isSuspended() + || $this->node->isUnderMaintenance() + || !$this->isInstalled() + || $this->status === self::STATUS_RESTORING_BACKUP + || !is_null($this->transfer) + ) { + throw new ServerStateConflictException($this); + } } /** - * Checks if the server is currently in a user-accessible state. If not, an + * Checks if the server is currently in a transferable state. If not, an * exception is raised. This should be called whenever something needs to make - * sure the server is not in a weird state that should block user access. - * - * @throws \Pterodactyl\Exceptions\Http\Server\ServerStateConflictException + * sure the server is able to be transferred and is not currently being transferred + * or installed. */ - public function validateCurrentState() + public function validateTransferState() { if ( - $this->isSuspended() || - !$this->isInstalled() || - $this->status === self::STATUS_RESTORING_BACKUP || - !is_null($this->transfer) + !$this->isInstalled() + || $this->status === self::STATUS_RESTORING_BACKUP + || !is_null($this->transfer) ) { throw new ServerStateConflictException($this); } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index 7e297b6015..f432d2e5ea 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property int $server_id @@ -15,12 +19,15 @@ * @property bool $archived * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\Server $server - * @property \Pterodactyl\Models\Node $oldNode - * @property \Pterodactyl\Models\Node $newNode + * @property Server $server + * @property Node $oldNode + * @property Node $newNode */ class ServerTransfer extends Model { + /** @use HasFactory<\Database\Factories\ServerTransferFactory> */ + use HasFactory; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -29,22 +36,16 @@ class ServerTransfer extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'server_transfers'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'server_id' => 'int', @@ -58,10 +59,7 @@ class ServerTransfer extends Model 'archived' => 'bool', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'old_node' => 'required|numeric', 'new_node' => 'required|numeric', @@ -77,9 +75,9 @@ class ServerTransfer extends Model /** * Gets the server associated with a server transfer. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -87,9 +85,9 @@ public function server() /** * Gets the source node associated with a server transfer. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Node, $this> */ - public function oldNode() + public function oldNode(): HasOne { return $this->hasOne(Node::class, 'id', 'old_node'); } @@ -97,9 +95,9 @@ public function oldNode() /** * Gets the target node associated with a server transfer. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Node, $this> */ - public function newNode() + public function newNode(): HasOne { return $this->hasOne(Node::class, 'id', 'new_node'); } diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 9b3ad81faf..eecdd4552d 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property int $server_id @@ -9,8 +11,8 @@ * @property string $variable_value * @property \Carbon\CarbonImmutable|null $created_at * @property \Carbon\CarbonImmutable|null $updated_at - * @property \Pterodactyl\Models\EggVariable $variable - * @property \Pterodactyl\Models\Server $server + * @property EggVariable $variable + * @property Server $server */ class ServerVariable extends Model { @@ -20,23 +22,18 @@ class ServerVariable extends Model */ public const RESOURCE_NAME = 'server_variable'; - /** @var bool */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** @var string */ protected $table = 'server_variables'; - /** @var string[] */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** @var string[] */ protected $casts = [ 'server_id' => 'integer', 'variable_id' => 'integer', ]; - /** @var string[] */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|int', 'variable_id' => 'required|int', 'variable_value' => 'string', @@ -45,9 +42,9 @@ class ServerVariable extends Model /** * Returns the server this variable is associated with. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -55,9 +52,9 @@ public function server() /** * Returns information about a given variables parent. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\EggVariable, $this> */ - public function variable() + public function variable(): BelongsTo { return $this->belongsTo(EggVariable::class, 'variable_id'); } diff --git a/app/Models/Session.php b/app/Models/Session.php index 535afc8016..baacdb8753 100644 --- a/app/Models/Session.php +++ b/app/Models/Session.php @@ -8,15 +8,11 @@ class Session extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'sessions'; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'string', diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 4582486285..b8812bc665 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -2,29 +2,25 @@ namespace Pterodactyl\Models; +/** + * Pterodactyl\Models\Setting. + * + * @property int $id + * @property string $key + * @property string $value + */ class Setting extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'settings'; - /** - * @var bool - */ public $timestamps = false; - /** - * @var array - */ protected $fillable = ['key', 'value']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'key' => 'required|string|between:1,191', 'value' => 'string', ]; diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 8dcdaf37ce..1d263b0dc7 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -3,6 +3,9 @@ namespace Pterodactyl\Models; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; /** * @property int $id @@ -11,11 +14,13 @@ * @property array $permissions * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\User $user - * @property \Pterodactyl\Models\Server $server + * @property User $user + * @property Server $server */ class Subuser extends Model { + /** @use HasFactory<\Database\Factories\SubuserFactory> */ + use HasFactory; use Notifiable; /** @@ -26,22 +31,16 @@ class Subuser extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'subusers'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'user_id' => 'int', @@ -49,10 +48,7 @@ class Subuser extends Model 'permissions' => 'array', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'user_id' => 'required|numeric|exists:users,id', 'server_id' => 'required|numeric|exists:servers,id', 'permissions' => 'nullable|array', @@ -61,10 +57,8 @@ class Subuser extends Model /** * Return a hashid encoded string to represent the ID of the subuser. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return app()->make('hashids')->encode($this->id); } @@ -72,9 +66,9 @@ public function getHashidAttribute() /** * Gets the server associated with a subuser. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Server, $this> */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -82,9 +76,9 @@ public function server() /** * Gets the user associated with a subuser. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } @@ -92,9 +86,9 @@ public function user() /** * Gets the permissions associated with a subuser. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Permission, $this> */ - public function permissions() + public function permissions(): HasMany { return $this->hasMany(Permission::class); } diff --git a/app/Models/Task.php b/app/Models/Task.php index 82ba72370f..50dab16eb3 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -4,6 +4,8 @@ use Illuminate\Container\Container; use Znck\Eloquent\Traits\BelongsToThrough; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Pterodactyl\Contracts\Extensions\HashidsInterface; /** @@ -18,11 +20,13 @@ * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property string $hashid - * @property \Pterodactyl\Models\Schedule $schedule - * @property \Pterodactyl\Models\Server $server + * @property Schedule $schedule + * @property Server $server */ class Task extends Model { + /** @use HasFactory<\Database\Factories\TaskFactory> */ + use HasFactory; use BelongsToThrough; /** @@ -40,22 +44,16 @@ class Task extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'tasks'; /** * Relationships to be updated when this model is updated. - * - * @var array */ protected $touches = ['schedule']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'schedule_id', @@ -69,8 +67,6 @@ class Task extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', @@ -83,8 +79,6 @@ class Task extends Model /** * Default attributes when creating a new model. - * - * @var array */ protected $attributes = [ 'time_offset' => 0, @@ -92,10 +86,7 @@ class Task extends Model 'continue_on_failure' => false, ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'schedule_id' => 'required|numeric|exists:schedules,id', 'sequence_id' => 'required|numeric|min:1', 'action' => 'required|string', @@ -105,12 +96,15 @@ class Task extends Model 'continue_on_failure' => 'boolean', ]; + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Return a hashid encoded string to represent the ID of the task. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } @@ -118,9 +112,9 @@ public function getHashidAttribute() /** * Return the schedule that a task belongs to. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Schedule, $this> */ - public function schedule() + public function schedule(): BelongsTo { return $this->belongsTo(Schedule::class); } @@ -128,12 +122,10 @@ public function schedule() /** * Return the server a task is assigned to, acts as a belongsToThrough. * - * @return \Znck\Eloquent\Relations\BelongsToThrough - * - * @throws \Exception + * @return \Znck\Eloquent\Relations\BelongsToThrough<\Pterodactyl\Models\Server, \Pterodactyl\Models\Schedule> */ - public function server() + public function server(): \Znck\Eloquent\Relations\BelongsToThrough { - return $this->belongsToThrough(Server::class, Schedule::class); + return $this->belongsToThrough(Server::class, Schedule::class); // @phpstan-ignore return.type } } diff --git a/app/Models/TaskLog.php b/app/Models/TaskLog.php index eabfde604c..81b7fdb787 100644 --- a/app/Models/TaskLog.php +++ b/app/Models/TaskLog.php @@ -8,33 +8,23 @@ class TaskLog extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'tasks_log'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', 'task_id' => 'integer', 'run_status' => 'integer', + 'run_time' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', ]; - - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $dates = ['run_time', 'created_at', 'updated_at']; } diff --git a/app/Models/Traits/HasAccessTokens.php b/app/Models/Traits/HasAccessTokens.php new file mode 100644 index 0000000000..224ba942af --- /dev/null +++ b/app/Models/Traits/HasAccessTokens.php @@ -0,0 +1,44 @@ + */ + use HasApiTokens { + tokens as private _tokens; + createToken as private _createToken; + } + + public function tokens(): HasMany + { + return $this->hasMany(Sanctum::$personalAccessTokenModel); + } + + public function createToken(?string $memo, ?array $ips): NewAccessToken + { + /** @var ApiKey $token */ + $token = $this->tokens()->forceCreate([ + 'user_id' => $this->id, + 'key_type' => ApiKey::TYPE_ACCOUNT, + 'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_ACCOUNT), + 'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)), + 'memo' => $memo ?? '', + 'allowed_ips' => $ips ?? [], + ]); + + return new NewAccessToken($token, $plain); + } +} diff --git a/app/Models/Traits/HasRealtimeIdentifier.php b/app/Models/Traits/HasRealtimeIdentifier.php new file mode 100644 index 0000000000..ea1ea31816 --- /dev/null +++ b/app/Models/Traits/HasRealtimeIdentifier.php @@ -0,0 +1,73 @@ +getRawOriginal(static::$identifierDataColumn))->getBytes(); + + return sprintf('%s_%s', static::$identifierPrefix, Base32::encodeUnpadded($bytes)); + }); + } + + public function scopeWhereIdentifier(Builder $builder, string $identifier): void + { + if (!str_starts_with($identifier, $prefix = self::$identifierPrefix . '_')) { + $builder->whereRaw('0 = 1'); + + return; + } + + $bytes = rescue(fn () => Base32::decode(Str::replaceFirst($prefix, '', $identifier)), report: false); + if (empty($bytes)) { + $builder->whereRaw('0 = 1'); + + return; + } + + $builder->where(self::$identifierDataColumn, Uuid::fromBytes($bytes)->toString()); + } + + protected static function bootHasRealtimeIdentifier(): void + { + $attrs = (new \ReflectionClass(static::class))->getAttributes(Identifiable::class); + + Assert::count( + $attrs, + 1, + 'The #[' . Identifiable::class . '] attribute must be set on ' . static::class . ' to use realtime identifiers.' + ); + + $instance = $attrs[0]->newInstance(); + + self::$identifierPrefix = $instance->prefix; + self::$identifierDataColumn = $instance->column; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 6cdc414f25..e0de1d4251 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,20 +3,29 @@ namespace Pterodactyl\Models; use Pterodactyl\Rules\Username; +use Pterodactyl\Facades\Activity; use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Builder; +use Pterodactyl\Contracts\Models\Identifiable; +use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; use Pterodactyl\Traits\Helpers\AvailableLanguages; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; +use Pterodactyl\Models\Traits\HasRealtimeIdentifier; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; /** + * Pterodactyl\Models\User. + * * @property int $id * @property string|null $external_id * @property string $uuid @@ -25,30 +34,70 @@ * @property string|null $name_first * @property string|null $name_last * @property string $password - * @property string|null $remeber_token + * @property string|null $remember_token * @property string $language * @property bool $root_admin * @property bool $use_totp * @property string|null $totp_secret - * @property \Carbon\Carbon|null $totp_authenticated_at + * @property \Illuminate\Support\Carbon|null $totp_authenticated_at * @property bool $gravatar - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys + * @property int|null $api_keys_count * @property string $name - * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys - * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens + * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property int|null $notifications_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens + * @property int|null $recovery_tokens_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers + * @property int|null $servers_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys + * @property int|null $ssh_keys_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $tokens + * @property int|null $tokens_count + * + * @method static \Database\Factories\UserFactory factory(...$parameters) + * @method static Builder|User newModelQuery() + * @method static Builder|User newQuery() + * @method static Builder|User query() + * @method static Builder|User whereCreatedAt($value) + * @method static Builder|User whereEmail($value) + * @method static Builder|User whereExternalId($value) + * @method static Builder|User whereGravatar($value) + * @method static Builder|User whereId($value) + * @method static Builder|User whereLanguage($value) + * @method static Builder|User whereNameFirst($value) + * @method static Builder|User whereNameLast($value) + * @method static Builder|User wherePassword($value) + * @method static Builder|User whereRememberToken($value) + * @method static Builder|User whereRootAdmin($value) + * @method static Builder|User whereTotpAuthenticatedAt($value) + * @method static Builder|User whereTotpSecret($value) + * @method static Builder|User whereUpdatedAt($value) + * @method static Builder|User whereUseTotp($value) + * @method static Builder|User whereUsername($value) + * @method static Builder|User whereUuid($value) + * + * @mixin \Eloquent */ +#[Attributes\Identifiable('user')] class User extends Model implements AuthenticatableContract, AuthorizableContract, - CanResetPasswordContract + CanResetPasswordContract, + Identifiable { use Authenticatable; use Authorizable; use AvailableLanguages; use CanResetPassword; + /** @use \Pterodactyl\Models\Traits\HasAccessTokens<\Pterodactyl\Models\ApiKey> */ + use HasAccessTokens; use Notifiable; + /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ + use HasFactory; + use HasRealtimeIdentifier; public const USER_LEVEL_USER = 0; public const USER_LEVEL_ADMIN = 1; @@ -61,22 +110,16 @@ class User extends Model implements /** * Level of servers to display when using access() on a user. - * - * @var string */ - protected $accessLevel = 'all'; + protected string $accessLevel = 'all'; /** * The table associated with the model. - * - * @var string */ protected $table = 'users'; /** * A list of mass-assignable variables. - * - * @var array */ protected $fillable = [ 'external_id', @@ -95,31 +138,21 @@ class User extends Model implements /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'root_admin' => 'boolean', 'use_totp' => 'boolean', 'gravatar' => 'boolean', + 'totp_authenticated_at' => 'datetime', ]; - /** - * @var array - */ - protected $dates = ['totp_authenticated_at']; - /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at']; /** * Default values for specific fields in the database. - * - * @var array */ protected $attributes = [ 'external_id' => null, @@ -131,12 +164,10 @@ class User extends Model implements /** * Rules verifying that the data being stored matches the expectations of the database. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'uuid' => 'required|string|size:36|unique:users,uuid', - 'email' => 'required|email|between:1,191|unique:users,email', + 'email' => 'required|email:strict|between:1,191|unique:users,email', 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', 'username' => 'required|between:1,191|unique:users,username', 'name_first' => 'required|string|between:1,191', @@ -152,7 +183,7 @@ class User extends Model implements * Implement language verification by overriding Eloquence's gather * rules function. */ - public static function getRules() + public static function getRules(): array { $rules = parent::getRules(); @@ -167,7 +198,9 @@ public static function getRules() */ public function toVueObject(): array { - return (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray(); + return Collection::make($this->toArray())->except(['id', 'external_id']) + ->merge(['identifier' => $this->identifier]) + ->toArray(); } /** @@ -177,6 +210,11 @@ public function toVueObject(): array */ public function sendPasswordResetNotification($token) { + Activity::event('auth:reset-password') + ->withRequestMetadata() + ->subject($this) + ->log('sending password reset email'); + $this->notify(new ResetPasswordNotification($token)); } @@ -190,10 +228,8 @@ public function setUsernameAttribute(string $value) /** * Return a concatenated result for the accounts full name. - * - * @return string */ - public function getNameAttribute() + public function getNameAttribute(): string { return trim($this->name_first . ' ' . $this->name_last); } @@ -201,37 +237,56 @@ public function getNameAttribute() /** * Returns all servers that a user owns. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Server, $this> */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class, 'owner_id'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\ApiKey, $this> */ - public function apiKeys() + public function apiKeys(): HasMany { return $this->hasMany(ApiKey::class) ->where('key_type', ApiKey::TYPE_ACCOUNT); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\RecoveryToken, $this> */ - public function recoveryTokens() + public function recoveryTokens(): HasMany { return $this->hasMany(RecoveryToken::class); } /** - * Returns all of the servers that a user can access by way of being the owner of the + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\UserSSHKey, $this> + */ + public function sshKeys(): HasMany + { + return $this->hasMany(UserSSHKey::class); + } + + /** + * Returns all the activity logs where this user is the subject — not to + * be confused by activity logs where this user is the _actor_. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\Pterodactyl\Models\ActivityLog, $this> + */ + public function activity(): MorphToMany + { + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); + } + + /** + * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder<\Pterodactyl\Models\Server> */ - public function accessibleServers() + public function accessibleServers(): Builder { return Server::query() ->select('servers.*') diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php new file mode 100644 index 0000000000..d219f04079 --- /dev/null +++ b/app/Models/UserSSHKey.php @@ -0,0 +1,69 @@ + */ + use HasFactory; + use SoftDeletes; + + public const RESOURCE_NAME = 'ssh_key'; + + protected $table = 'user_ssh_keys'; + + protected $fillable = [ + 'name', + 'public_key', + 'fingerprint', + ]; + + public static array $validationRules = [ + 'name' => ['required', 'string'], + 'fingerprint' => ['required', 'string'], + 'public_key' => ['required', 'string'], + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this> + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index e52bed5fec..fb13380121 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -12,50 +12,25 @@ class AccountCreated extends Notification implements ShouldQueue { use Queueable; - /** - * The authentication token to be used for the user to set their - * password for the first time. - * - * @var string|null - */ - public $token; - - /** - * The user model for the created user. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new notification instance. */ - public function __construct(User $user, string $token = null) + public function __construct(public User $user, public ?string $token = null) { - $this->token = $token; - $this->user = $user; } /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { $message = (new MailMessage()) ->greeting('Hello ' . $this->user->name . '!') diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php index c116a2e294..c78c7db8be 100644 --- a/app/Notifications/AddedToServer.php +++ b/app/Notifications/AddedToServer.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,10 +11,7 @@ class AddedToServer extends Notification implements ShouldQueue { use Queueable; - /** - * @var object - */ - public $server; + public object $server; /** * Create a new notification instance. @@ -33,24 +23,16 @@ public function __construct(array $server) /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { return (new MailMessage()) ->greeting('Hello ' . $this->server->user . '!') diff --git a/app/Notifications/MailTested.php b/app/Notifications/MailTested.php index d0f083accd..892ff7b0a2 100644 --- a/app/Notifications/MailTested.php +++ b/app/Notifications/MailTested.php @@ -8,22 +8,16 @@ class MailTested extends Notification { - /** - * @var \Pterodactyl\Models\User - */ - private $user; - - public function __construct(User $user) + public function __construct(private User $user) { - $this->user = $user; } - public function via() + public function via(): array { return ['mail']; } - public function toMail() + public function toMail(): MailMessage { return (new MailMessage()) ->subject('Pterodactyl Test Message') diff --git a/app/Notifications/RemovedFromServer.php b/app/Notifications/RemovedFromServer.php index 24418e51ce..492dc60e6d 100644 --- a/app/Notifications/RemovedFromServer.php +++ b/app/Notifications/RemovedFromServer.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,10 +11,7 @@ class RemovedFromServer extends Notification implements ShouldQueue { use Queueable; - /** - * @var object - */ - public $server; + public object $server; /** * Create a new notification instance. @@ -33,24 +23,16 @@ public function __construct(array $server) /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { return (new MailMessage()) ->error() diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index cd6d46050a..b424c8293b 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,43 +11,25 @@ class SendPasswordReset extends Notification implements ShouldQueue { use Queueable; - /** - * The password reset token. - * - * @var string - */ - public $token; - /** * Create a new notification instance. - * - * @param string $token */ - public function __construct($token) + public function __construct(public string $token) { - $this->token = $token; } /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(mixed $notifiable): MailMessage { return (new MailMessage()) ->subject('Reset Password') diff --git a/app/Notifications/ServerInstalled.php b/app/Notifications/ServerInstalled.php index cc5a943138..037633786c 100644 --- a/app/Notifications/ServerInstalled.php +++ b/app/Notifications/ServerInstalled.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Notifications; +use Pterodactyl\Models\User; use Illuminate\Bus\Queueable; use Pterodactyl\Events\Event; +use Pterodactyl\Models\Server; use Illuminate\Container\Container; +use Pterodactyl\Events\Server\Installed; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Pterodactyl\Contracts\Core\ReceivesEvents; @@ -15,23 +18,17 @@ class ServerInstalled extends Notification implements ShouldQueue, ReceivesEvent { use Queueable; - /** - * @var \Pterodactyl\Models\Server - */ - public $server; + public Server $server; - /** - * @var \Pterodactyl\Models\User - */ - public $user; + public User $user; /** * Handle a direct call to this notification from the server installed event. This is configured * in the event service provider. * - * @param \Pterodactyl\Events\Event|\Pterodactyl\Events\Server\Installed $event + * @phpstan-param Installed $event */ - public function handle(Event $event): void + public function handle(Event|Installed $event): void { $event->server->loadMissing('user'); @@ -45,20 +42,16 @@ public function handle(Event $event): void /** * Get the notification's delivery channels. - * - * @return array */ - public function via() + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail() + public function toMail(): MailMessage { return (new MailMessage()) ->greeting('Hello ' . $this->user->username . '.') diff --git a/app/Observers/EggVariableObserver.php b/app/Observers/EggVariableObserver.php new file mode 100644 index 0000000000..3ae3a89171 --- /dev/null +++ b/app/Observers/EggVariableObserver.php @@ -0,0 +1,24 @@ +field_type) { + unset($variable->field_type); + } + } + + public function updating(EggVariable $variable): void + { + // @phpstan-ignore-next-line property.notFound + if ($variable->field_type) { + unset($variable->field_type); + } + } +} diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php index c6a6212048..51276523ef 100644 --- a/app/Observers/ServerObserver.php +++ b/app/Observers/ServerObserver.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Observers; @@ -20,7 +13,7 @@ class ServerObserver /** * Listen to the Server creating event. */ - public function creating(Server $server) + public function creating(Server $server): void { event(new Events\Server\Creating($server)); } @@ -28,7 +21,7 @@ public function creating(Server $server) /** * Listen to the Server created event. */ - public function created(Server $server) + public function created(Server $server): void { event(new Events\Server\Created($server)); } @@ -36,7 +29,7 @@ public function created(Server $server) /** * Listen to the Server deleting event. */ - public function deleting(Server $server) + public function deleting(Server $server): void { event(new Events\Server\Deleting($server)); } @@ -44,7 +37,7 @@ public function deleting(Server $server) /** * Listen to the Server deleted event. */ - public function deleted(Server $server) + public function deleted(Server $server): void { event(new Events\Server\Deleted($server)); } @@ -52,7 +45,7 @@ public function deleted(Server $server) /** * Listen to the Server saving event. */ - public function saving(Server $server) + public function saving(Server $server): void { event(new Events\Server\Saving($server)); } @@ -60,7 +53,7 @@ public function saving(Server $server) /** * Listen to the Server saved event. */ - public function saved(Server $server) + public function saved(Server $server): void { event(new Events\Server\Saved($server)); } @@ -68,7 +61,7 @@ public function saved(Server $server) /** * Listen to the Server updating event. */ - public function updating(Server $server) + public function updating(Server $server): void { event(new Events\Server\Updating($server)); } @@ -76,7 +69,7 @@ public function updating(Server $server) /** * Listen to the Server saved event. */ - public function updated(Server $server) + public function updated(Server $server): void { event(new Events\Server\Updated($server)); } diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index 128de77fb3..f1e028b96f 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Observers; @@ -19,7 +12,7 @@ class SubuserObserver /** * Listen to the Subuser creating event. */ - public function creating(Subuser $subuser) + public function creating(Subuser $subuser): void { event(new Events\Subuser\Creating($subuser)); } @@ -27,21 +20,21 @@ public function creating(Subuser $subuser) /** * Listen to the Subuser created event. */ - public function created(Subuser $subuser) + public function created(Subuser $subuser): void { event(new Events\Subuser\Created($subuser)); - $subuser->user->notify((new AddedToServer([ + $subuser->user->notify(new AddedToServer([ 'user' => $subuser->user->name_first, 'name' => $subuser->server->name, 'uuidShort' => $subuser->server->uuidShort, - ]))); + ])); } /** * Listen to the Subuser deleting event. */ - public function deleting(Subuser $subuser) + public function deleting(Subuser $subuser): void { event(new Events\Subuser\Deleting($subuser)); } @@ -49,13 +42,13 @@ public function deleting(Subuser $subuser) /** * Listen to the Subuser deleted event. */ - public function deleted(Subuser $subuser) + public function deleted(Subuser $subuser): void { event(new Events\Subuser\Deleted($subuser)); - $subuser->user->notify((new RemovedFromServer([ + $subuser->user->notify(new RemovedFromServer([ 'user' => $subuser->user->name_first, 'name' => $subuser->server->name, - ]))); + ])); } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 18d73bc106..dd017a22d3 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -7,12 +7,12 @@ class UserObserver { - protected $uuid; + protected string $uuid; /** * Listen to the User creating event. */ - public function creating(User $user) + public function creating(User $user): void { event(new Events\User\Creating($user)); } @@ -20,7 +20,7 @@ public function creating(User $user) /** * Listen to the User created event. */ - public function created(User $user) + public function created(User $user): void { event(new Events\User\Created($user)); } @@ -28,7 +28,7 @@ public function created(User $user) /** * Listen to the User deleting event. */ - public function deleting(User $user) + public function deleting(User $user): void { event(new Events\User\Deleting($user)); } @@ -36,7 +36,7 @@ public function deleting(User $user) /** * Listen to the User deleted event. */ - public function deleted(User $user) + public function deleted(User $user): void { event(new Events\User\Deleted($user)); } diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index c821bb42fb..e3565bb250 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -2,55 +2,28 @@ namespace Pterodactyl\Policies; -use Carbon\Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Cache\Repository as CacheRepository; class ServerPolicy { - /** - * @var \Illuminate\Contracts\Cache\Repository - */ - private $cache; - - /** - * ServerPolicy constructor. - */ - public function __construct(CacheRepository $cache) - { - $this->cache = $cache; - } - /** * Checks if the user has the given permission on/for the server. - * - * @param string $permission - * - * @return bool */ - protected function checkPermission(User $user, Server $server, $permission) + protected function checkPermission(User $user, Server $server, string $permission): bool { - $key = sprintf('ServerPolicy.%s.%s', $user->uuid, $server->uuid); - - $permissions = $this->cache->remember($key, Carbon::now()->addSeconds(5), function () use ($user, $server) { - /** @var \Pterodactyl\Models\Subuser|null $subuser */ - $subuser = $server->subusers()->where('user_id', $user->id)->first(); - - return $subuser ? $subuser->permissions : []; - }); + $subuser = $server->subusers->where('user_id', $user->id)->first(); + if (!$subuser || empty($permission)) { + return false; + } - return in_array($permission, $permissions); + return in_array($permission, $subuser->permissions); } /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. - * - * @param string $ability - * - * @return bool */ - public function before(User $user, $ability, Server $server) + public function before(User $user, string $ability, Server $server): bool { if ($user->root_admin || $server->owner_id === $user->id) { return true; @@ -63,11 +36,8 @@ public function before(User $user, $ability, Server $server) * This is a horrendous hack to avoid Laravel's "smart" behavior that does * not call the before() function if there isn't a function matching the * policy permission. - * - * @param string $name - * @param mixed $arguments */ - public function __call($name, $arguments) + public function __call(string $name, mixed $arguments) { // do nothing } diff --git a/app/Providers/ActivityLogServiceProvider.php b/app/Providers/ActivityLogServiceProvider.php new file mode 100644 index 0000000000..7a3de759c6 --- /dev/null +++ b/app/Providers/ActivityLogServiceProvider.php @@ -0,0 +1,20 @@ +app->scoped(ActivityLogBatchService::class); + $this->app->scoped(ActivityLogTargetableService::class); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4ccc78641e..63c12014be 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,34 +2,26 @@ namespace Pterodactyl\Providers; -use View; -use Cache; +use Pterodactyl\Models; use Illuminate\Support\Str; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Observers\UserObserver; use Pterodactyl\Extensions\Themes\Theme; -use Pterodactyl\Observers\ServerObserver; -use Pterodactyl\Observers\SubuserObserver; +use Illuminate\Database\Eloquent\Relations\Relation; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. */ - public function boot() + public function boot(): void { Schema::defaultStringLength(191); - User::observe(UserObserver::class); - Server::observe(ServerObserver::class); - Subuser::observe(SubuserObserver::class); - View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); View::share('appIsGit', $this->versionData()['is_git'] ?? false); @@ -43,12 +35,26 @@ public function boot() if (Str::startsWith(config('app.url') ?? '', 'https://')) { URL::forceScheme('https'); } + + Relation::enforceMorphMap([ + 'allocation' => Models\Allocation::class, + 'api_key' => Models\ApiKey::class, + 'backup' => Models\Backup::class, + 'database' => Models\Database::class, + 'egg' => Models\Egg::class, + 'egg_variable' => Models\EggVariable::class, + 'schedule' => Models\Schedule::class, + 'server' => Models\Server::class, + 'ssh_key' => Models\UserSSHKey::class, + 'task' => Models\Task::class, + 'user' => Models\User::class, + ]); } /** * Register application service providers. */ - public function register() + public function register(): void { // Only load the settings service provider if the environment // is configured to allow it. @@ -63,10 +69,8 @@ public function register() /** * Return version information for the footer. - * - * @return array */ - protected function versionData() + protected function versionData(): array { return Cache::remember('git-version', 5, function () { if (file_exists(base_path('.git/HEAD'))) { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e147736ede..28222f0067 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,24 +2,23 @@ namespace Pterodactyl\Providers; +use Laravel\Sanctum\Sanctum; +use Pterodactyl\Models\ApiKey; +use Pterodactyl\Models\Server; +use Pterodactyl\Policies\ServerPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** - * The policy mappings for the application. - * - * @var array + * The model to policy mappings for the application. */ protected $policies = [ - 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', + Server::class => ServerPolicy::class, ]; - /** - * Register any application authentication / authorization services. - */ - public function boot() + public function boot(): void { - $this->registerPolicies(); + Sanctum::usePersonalAccessTokenModel(ApiKey::class); } } diff --git a/app/Providers/BackupsServiceProvider.php b/app/Providers/BackupsServiceProvider.php index 999dfa90d4..25ce9bd7a4 100644 --- a/app/Providers/BackupsServiceProvider.php +++ b/app/Providers/BackupsServiceProvider.php @@ -11,17 +11,14 @@ class BackupsServiceProvider extends ServiceProvider implements DeferrableProvid /** * Register the S3 backup disk. */ - public function register() + public function register(): void { $this->app->singleton(BackupManager::class, function ($app) { return new BackupManager($app); }); } - /** - * @return string[] - */ - public function provides() + public function provides(): array { return [BackupManager::class]; } diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php index 16a17a3731..3512acfd9e 100644 --- a/app/Providers/BladeServiceProvider.php +++ b/app/Providers/BladeServiceProvider.php @@ -9,7 +9,7 @@ class BladeServiceProvider extends ServiceProvider /** * Perform post-registration booting of services. */ - public function boot() + public function boot(): void { $this->app->make('blade.compiler') ->directive('datetimeHuman', function ($expression) { diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index 3f7c84be45..0630a63774 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -10,7 +10,7 @@ class BroadcastServiceProvider extends ServiceProvider /** * Bootstrap any application services. */ - public function boot() + public function boot(): void { Broadcast::routes(); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5be9601d60..e5d7be3b3f 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,17 @@ namespace Pterodactyl\Providers; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Observers\UserObserver; +use Pterodactyl\Observers\ServerObserver; +use Pterodactyl\Observers\SubuserObserver; +use Pterodactyl\Listeners\TwoFactorListener; +use Pterodactyl\Listeners\RevocationListener; +use Pterodactyl\Observers\EggVariableObserver; +use Pterodactyl\Listeners\AuthenticationListener; use Pterodactyl\Events\Server\Installed as ServerInstalledEvent; use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -9,13 +20,30 @@ class EventServiceProvider extends ServiceProvider { /** - * The event listener mappings for the application. - * - * @var array + * The event to listener mappings for the application. */ protected $listen = [ - ServerInstalledEvent::class => [ - ServerInstalledNotification::class, - ], + ServerInstalledEvent::class => [ServerInstalledNotification::class], ]; + + protected $subscribe = [ + AuthenticationListener::class, + RevocationListener::class, + TwoFactorListener::class, + ]; + + protected static $shouldDiscoverEvents = false; + + /** + * Register any events for your application. + */ + public function boot(): void + { + parent::boot(); + + User::observe(UserObserver::class); + Server::observe(ServerObserver::class); + Subuser::observe(SubuserObserver::class); + EggVariable::observe(EggVariableObserver::class); + } } diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php index f2b3935d83..e2493e8e46 100644 --- a/app/Providers/HashidsServiceProvider.php +++ b/app/Providers/HashidsServiceProvider.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Providers; @@ -18,7 +11,7 @@ class HashidsServiceProvider extends ServiceProvider /** * Register the ability to use Hashids. */ - public function register() + public function register(): void { $this->app->singleton(HashidsInterface::class, function () { /** @var \Illuminate\Contracts\Config\Repository $config */ diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 8a0434f524..b06ca7a13c 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -41,9 +41,9 @@ class RepositoryServiceProvider extends ServiceProvider { /** - * Register all of the repository bindings. + * Register all the repository bindings. */ - public function register() + public function register(): void { // Eloquent Repositories $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 2dedacb4a1..8b48c033d0 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -3,59 +3,65 @@ namespace Pterodactyl\Providers; use Illuminate\Http\Request; +use Pterodactyl\Models\Database; +use Pterodactyl\Enum\ResourceLimit; use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; +use Pterodactyl\Http\Middleware\TrimStrings; +use Pterodactyl\Http\Middleware\AdminAuthenticate; +use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider { - /** - * This namespace is applied to the controller routes in your routes file. - * - * In addition, it is set as the URL generator's root namespace. - * - * @var string - */ - protected $namespace = 'Pterodactyl\Http\Controllers'; + protected const FILE_PATH_REGEX = '/^\/api\/client\/servers\/([a-z0-9-]{36})\/files(\/?$|\/(.)*$)/i'; /** * Define your route model bindings, pattern filters, etc. */ - public function boot() + public function boot(): void { $this->configureRateLimiting(); + // Disable trimming string values when requesting file information — it isn't helpful + // and messes up the ability to actually open a directory that ends with a space. + TrimStrings::skipWhen(function (Request $request) { + return preg_match(self::FILE_PATH_REGEX, $request->getPathInfo()) === 1; + }); + + // This is needed to make use of the "resolveRouteBinding" functionality in the + // model. Without it you'll never trigger that logic flow thus resulting in a 404 + // error because we request databases with a HashID, and not with a normal ID. + Route::model('database', Database::class); + $this->routes(function () { - Route::middleware(['web', 'auth', 'csrf']) - ->namespace("$this->namespace\\Base") - ->group(base_path('routes/base.php')); - - Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace("$this->namespace\\Admin") - ->group(base_path('routes/admin.php')); - - Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace("$this->namespace\\Auth") - ->group(base_path('routes/auth.php')); - - Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) - ->prefix('/api/server/{server}') - ->namespace("$this->namespace\\Server") - ->group(base_path('routes/server.php')); - - Route::middleware(['api', 'throttle:api.application']) - ->prefix('/api/application') - ->namespace("$this->namespace\\Api\\Application") - ->group(base_path('routes/api-application.php')); - - Route::middleware(['client-api', 'throttle:api.client']) - ->prefix('/api/client') - ->namespace("$this->namespace\\Api\\Client") - ->group(base_path('routes/api-client.php')); - - Route::middleware(['daemon'])->prefix('/api/remote') - ->namespace("$this->namespace\\Api\\Remote") + Route::middleware('web')->group(function () { + Route::middleware(['auth.session', RequireTwoFactorAuthentication::class]) + ->group(base_path('routes/base.php')); + + Route::middleware(['auth.session', RequireTwoFactorAuthentication::class, AdminAuthenticate::class]) + ->prefix('/admin') + ->group(base_path('routes/admin.php')); + + Route::middleware('guest')->prefix('/auth')->group(base_path('routes/auth.php')); + }); + + Route::middleware(['api', RequireTwoFactorAuthentication::class])->group(function () { + Route::middleware(['application-api', 'throttle:api.application']) + ->prefix('/api/application') + ->scopeBindings() + ->group(base_path('routes/api-application.php')); + + Route::middleware(['client-api', 'throttle:api.client']) + ->prefix('/api/client') + ->scopeBindings() + ->group(base_path('routes/api-client.php')); + }); + + Route::middleware('daemon') + ->prefix('/api/remote') + ->scopeBindings() ->group(base_path('routes/api-remote.php')); }); } @@ -63,7 +69,7 @@ public function boot() /** * Configure the rate limiters for the application. */ - protected function configureRateLimiting() + protected function configureRateLimiting(): void { // Authentication rate limiting. For login and checkpoint endpoints we'll apply // a limit of 10 requests per minute, for the forgot password endpoint apply a @@ -101,5 +107,7 @@ protected function configureRateLimiting() config('http.rate_limit.application') )->by($key); }); + + ResourceLimit::boot(); } } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 447ac3db13..dbcca85ea3 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -15,10 +15,8 @@ class SettingsServiceProvider extends ServiceProvider /** * An array of configuration keys to override with database values * if they exist. - * - * @var array */ - protected $keys = [ + protected array $keys = [ 'app:name', 'app:locale', 'recaptcha:enabled', @@ -37,37 +35,33 @@ class SettingsServiceProvider extends ServiceProvider /** * Keys specific to the mail driver that are only grabbed from the database * when using the SMTP driver. - * - * @var array */ - protected $emailKeys = [ - 'mail:host', - 'mail:port', + protected array $emailKeys = [ + 'mail:mailers:smtp:host', + 'mail:mailers:smtp:port', + 'mail:mailers:smtp:encryption', + 'mail:mailers:smtp:username', + 'mail:mailers:smtp:password', 'mail:from:address', 'mail:from:name', - 'mail:encryption', - 'mail:username', - 'mail:password', ]; /** * Keys that are encrypted and should be decrypted when set in the * configuration array. - * - * @var array */ - protected static $encrypted = [ - 'mail:password', + protected static array $encrypted = [ + 'mail:mailers:smtp:password', ]; /** * Boot the service provider. */ - public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings) + public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void { // Only set the email driver settings from the database if we // are configured using SMTP as the driver. - if ($config->get('mail.driver') === 'smtp') { + if ($config->get('mail.default') === 'smtp') { $this->keys = array_merge($this->keys, $this->emailKeys); } diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php index 9f484e0066..8ab7208c73 100644 --- a/app/Providers/ViewComposerServiceProvider.php +++ b/app/Providers/ViewComposerServiceProvider.php @@ -10,7 +10,7 @@ class ViewComposerServiceProvider extends ServiceProvider /** * Register bindings in the container. */ - public function boot() + public function boot(): void { $this->app->make('view')->composer('*', AssetComposer::class); } diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index 3723c1d1fa..6eb8b6d1e5 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -10,16 +10,14 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Allocation::class; } /** - * Return all of the allocations that exist for a node that are not currently + * Return all the allocations that exist for a node that are not currently * allocated. */ public function getUnassignedAllocationIds(int $node): array @@ -57,10 +55,8 @@ protected function getDiscardableDedicatedAllocations(array $nodes = []): array /** * Return a single allocation from those meeting the requirements. - * - * @return \Pterodactyl\Models\Allocation|null */ - public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false) + public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false): ?Allocation { $query = Allocation::query()->whereNull('server_id'); diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php index 67a7e6e6e9..5a63514fe0 100644 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -11,16 +11,14 @@ class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInt { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return ApiKey::class; } /** - * Get all of the account API keys that exist for a specific user. + * Get all the account API keys that exist for a specific user. */ public function getAccountKeys(User $user): Collection { @@ -29,16 +27,6 @@ public function getAccountKeys(User $user): Collection ->get($this->getColumns()); } - /** - * Get all of the application API keys that exist for a specific user. - */ - public function getApplicationKeys(User $user): Collection - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->get($this->getColumns()); - } - /** * Delete an account API key from the panel for a specific user. */ @@ -49,15 +37,4 @@ public function deleteAccountKey(User $user, string $identifier): int ->where('identifier', $identifier) ->delete(); } - - /** - * Delete an application API key from the panel for a specific user. - */ - public function deleteApplicationKey(User $user, string $identifier): int - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->where('identifier', $identifier) - ->delete(); - } } diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php index 051d0ce424..fa79f70981 100644 --- a/app/Repositories/Eloquent/BackupRepository.php +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -5,24 +5,20 @@ use Carbon\Carbon; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; +use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; class BackupRepository extends EloquentRepository { - /** - * @return string - */ - public function model() + public function model(): string { return Backup::class; } /** * Determines if too many backups have been generated by the server. - * - * @return \Pterodactyl\Models\Backup[]|\Illuminate\Support\Collection */ - public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 600) + public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 600): array|Collection { return $this->getBuilder() ->withTrashed() @@ -38,6 +34,8 @@ public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 60 /** * Returns a query filtering only non-failed backups for a specific server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Backup, \Pterodactyl\Models\Server> */ public function getNonFailedBackups(Server $server): HasMany { diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php index c55b03232d..2c2b9dcde7 100644 --- a/app/Repositories/Eloquent/DatabaseHostRepository.php +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -10,10 +10,8 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return DatabaseHost::class; } diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index 53e590df9c..9bc33d035e 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -11,58 +11,44 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositoryInterface { - /** - * @var string - */ - protected $connection = self::DEFAULT_CONNECTION_NAME; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; + protected string $connection = self::DEFAULT_CONNECTION_NAME; /** * DatabaseRepository constructor. */ - public function __construct(Application $application, DatabaseManager $database) + public function __construct(Application $application, private DatabaseManager $database) { parent::__construct($application); - - $this->database = $database; } /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Database::class; } /** - * Set the connection name to execute statements against. - * - * @return $this + * Return the connection to execute statements against. */ - public function setConnection(string $connection) + public function getConnection(): string { - $this->connection = $connection; - - return $this; + return $this->connection; } /** - * Return the connection to execute statements against. + * Set the connection name to execute statements against. */ - public function getConnection(): string + public function setConnection(string $connection): self { - return $this->connection; + $this->connection = $connection; + + return $this; } /** - * Return all of the databases belonging to a server. + * Return all the databases belonging to a server. */ public function getDatabasesForServer(int $server): Collection { @@ -70,7 +56,7 @@ public function getDatabasesForServer(int $server): Collection } /** - * Return all of the databases for a given host with the server relationship loaded. + * Return all the databases for a given host with the server relationship loaded. */ public function getDatabasesForHost(int $host, int $count = 25): LengthAwarePaginator { @@ -89,16 +75,18 @@ public function createDatabase(string $database): bool /** * Create a new database user on a given connection. - * - * @param $max_connections */ - public function createUser(string $username, string $remote, string $password, $max_connections): bool + public function createUser(string $username, string $remote, string $password, ?int $max_connections): bool { - if (!$max_connections) { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); - } else { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); + $args = [$username, $remote, $password]; + $command = 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\''; + + if (!empty($max_connections)) { + $args[] = $max_connections; + $command .= ' WITH MAX_USER_CONNECTIONS %s'; } + + return $this->run(sprintf($command, ...$args)); } /** @@ -132,8 +120,6 @@ public function dropDatabase(string $database): bool /** * Drop a given user on a specific connection. - * - * @return mixed */ public function dropUser(string $username, string $remote): bool { diff --git a/app/Repositories/Eloquent/EggRepository.php b/app/Repositories/Eloquent/EggRepository.php index 98b7db4530..0e09ab20fa 100644 --- a/app/Repositories/Eloquent/EggRepository.php +++ b/app/Repositories/Eloquent/EggRepository.php @@ -13,10 +13,8 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Egg::class; } @@ -24,13 +22,13 @@ public function model() /** * Return an egg with the variables relation attached. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithVariables(int $id): Egg { try { return $this->getBuilder()->with('variables')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -48,29 +46,29 @@ public function getAllWithCopyAttributes(): Collection * * @param int|string $value * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithCopyAttributes($value, string $column = 'id'): Egg { - Assert::true((is_digit($value) || is_string($value)), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.'); + Assert::true(is_digit($value) || is_string($value), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.'); try { return $this->getBuilder()->with('scriptFrom', 'configFrom')->where($column, '=', $value)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } /** - * Return all of the data needed to export a service. + * Return all the data needed to export a service. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithExportAttributes(int $id): Egg { try { return $this->getBuilder()->with('scriptFrom', 'configFrom', 'variables')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/EggVariableRepository.php b/app/Repositories/Eloquent/EggVariableRepository.php index ce79a65f6d..8ca35debbb 100644 --- a/app/Repositories/Eloquent/EggVariableRepository.php +++ b/app/Repositories/Eloquent/EggVariableRepository.php @@ -10,10 +10,8 @@ class EggVariableRepository extends EloquentRepository implements EggVariableRep { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return EggVariable::class; } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index 3d3e09cdcf..443accfd36 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Repositories\Repository; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Expression; @@ -16,21 +17,14 @@ abstract class EloquentRepository extends Repository implements RepositoryInterface { - /** - * @var bool - */ - protected $useRequestFilters = false; + protected bool $useRequestFilters = false; /** * Determines if the repository function should use filters off the request object * present when returning results. This allows repository methods to be called in API * context's such that we can pass through ?filter[name]=Dane&sort=desc for example. - * - * @param bool $usingFilters - * - * @return $this */ - public function usingRequestFilters($usingFilters = true) + public function usingRequestFilters(bool $usingFilters = true): self { $this->useRequestFilters = $usingFilters; @@ -39,20 +33,16 @@ public function usingRequestFilters($usingFilters = true) /** * Returns the request instance. - * - * @return \Illuminate\Http\Request */ - protected function request() + protected function request(): Request { return $this->app->make(Request::class); } /** * Paginate the response data based on the page para. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - protected function paginate(Builder $instance, int $default = 50) + protected function paginate(Builder $instance, int $default = 50): LengthAwarePaginator { if (!$this->useRequestFilters) { return $instance->paginate($default); @@ -64,20 +54,16 @@ protected function paginate(Builder $instance, int $default = 50) /** * Return an instance of the eloquent model bound to this * repository instance. - * - * @return \Illuminate\Database\Eloquent\Model */ - public function getModel() + public function getModel(): Model { return $this->model; } /** * Return an instance of the builder to use for this repository. - * - * @return \Illuminate\Database\Eloquent\Builder */ - public function getBuilder() + public function getBuilder(): Builder { return $this->getModel()->newQuery(); } @@ -85,11 +71,9 @@ public function getBuilder() /** * Create a new record in the database and return the associated model. * - * @return \Illuminate\Database\Eloquent\Model|bool - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws DataValidationException */ - public function create(array $fields, bool $validate = true, bool $force = false) + public function create(array $fields, bool $validate = true, bool $force = false): Model|bool { $instance = $this->getBuilder()->newModelInstance(); ($force) ? $instance->forceFill($fields) : $instance->fill($fields); @@ -98,7 +82,7 @@ public function create(array $fields, bool $validate = true, bool $force = false $saved = $instance->skipValidation()->save(); } else { if (!$saved = $instance->save()) { - throw new DataValidationException($instance->getValidator()); + throw new DataValidationException($instance->getValidator(), $instance); } } @@ -108,15 +92,13 @@ public function create(array $fields, bool $validate = true, bool $force = false /** * Find a model that has the specific ID passed. * - * @return \Illuminate\Database\Eloquent\Model - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ - public function find(int $id) + public function find(int $id): Model { try { return $this->getBuilder()->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -132,15 +114,13 @@ public function findWhere(array $fields): Collection /** * Find and return the first matching instance for the given fields. * - * @return \Illuminate\Database\Eloquent\Model - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ - public function findFirstWhere(array $fields) + public function findFirstWhere(array $fields): Model { try { return $this->getBuilder()->where($fields)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -174,18 +154,14 @@ public function deleteWhere(array $attributes, bool $force = false): int /** * Update a given ID with the passed array of fields. * - * @param int $id - * - * @return \Illuminate\Database\Eloquent\Model|bool - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws DataValidationException + * @throws RecordNotFoundException */ - public function update($id, array $fields, bool $validate = true, bool $force = false) + public function update(int $id, array $fields, bool $validate = true, bool $force = false): Model|bool { try { $instance = $this->getBuilder()->where('id', $id)->firstOrFail(); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } @@ -195,7 +171,7 @@ public function update($id, array $fields, bool $validate = true, bool $force = $saved = $instance->skipValidation()->save(); } else { if (!$saved = $instance->save()) { - throw new DataValidationException($instance->getValidator()); + throw new DataValidationException($instance->getValidator(), $instance); } } @@ -204,12 +180,8 @@ public function update($id, array $fields, bool $validate = true, bool $force = /** * Update a model using the attributes passed. - * - * @param array|\Closure $attributes - * - * @return int */ - public function updateWhere($attributes, array $values) + public function updateWhere(array $attributes, array $values): int { return $this->getBuilder()->where($attributes)->update($values); } @@ -228,12 +200,10 @@ public function updateWhereIn(string $column, array $values, array $fields): int /** * Update a record if it exists in the database, otherwise create it. * - * @return \Illuminate\Database\Eloquent\Model - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws DataValidationException + * @throws RecordNotFoundException */ - public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false) + public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false): Model|bool { foreach ($where as $item) { Assert::true(is_scalar($item) || is_null($item), 'First argument passed to updateOrCreate should be an array of scalar or null values, received an array value of %s.'); @@ -241,7 +211,7 @@ public function updateOrCreate(array $where, array $fields, bool $validate = tru try { $instance = $this->setColumns('id')->findFirstWhere($where); - } catch (RecordNotFoundException $exception) { + } catch (RecordNotFoundException) { return $this->create(array_merge($where, $fields), $validate, $force); } diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index c06d10a9f0..066f6d2fa2 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -12,10 +12,8 @@ class LocationRepository extends EloquentRepository implements LocationRepositor { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Location::class; } @@ -29,7 +27,7 @@ public function getAllWithDetails(): Collection } /** - * Return all of the available locations with the nodes as a relationship. + * Return all the available locations with the nodes as a relationship. */ public function getAllWithNodes(): Collection { @@ -37,17 +35,15 @@ public function getAllWithNodes(): Collection } /** - * Return all of the nodes and their respective count of servers for a location. - * - * @return mixed + * Return all the nodes and their respective count of servers for a location. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithNodes(int $id): Location { try { return $this->getBuilder()->with('nodes.servers')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -55,15 +51,13 @@ public function getWithNodes(int $id): Location /** * Return a location and the count of nodes in that location. * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithNodeCount(int $id): Location { try { return $this->getBuilder()->withCount('nodes')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/MountRepository.php b/app/Repositories/Eloquent/MountRepository.php index 490f76b649..f0983aa0bf 100644 --- a/app/Repositories/Eloquent/MountRepository.php +++ b/app/Repositories/Eloquent/MountRepository.php @@ -12,10 +12,8 @@ class MountRepository extends EloquentRepository { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Mount::class; } @@ -29,11 +27,9 @@ public function getAllWithDetails(): Collection } /** - * Return all of the mounts and their respective relations. - * - * @return mixed + * Return all the mounts and their respective relations. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithRelations(string $id): Mount { diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php index 6d4992e454..6b8d75987a 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Nest; +use Illuminate\Database\Eloquent\Collection; use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; @@ -17,10 +11,8 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Nest::class; } @@ -28,13 +20,11 @@ public function model() /** * Return a nest or all nests with their associated eggs and variables. * - * @param int $id - * - * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ - public function getWithEggs(int $id = null) + public function getWithEggs(?int $id = null): Collection|Nest { $instance = $this->getBuilder()->with('eggs', 'eggs.variables'); @@ -53,11 +43,11 @@ public function getWithEggs(int $id = null) /** * Return a nest or all nests and the count of eggs and servers for that nest. * - * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ - public function getWithCounts(int $id = null) + public function getWithCounts(?int $id = null): Collection|Nest { $instance = $this->getBuilder()->withCount(['eggs', 'servers']); @@ -76,7 +66,7 @@ public function getWithCounts(int $id = null) /** * Return a nest along with its associated eggs and the servers relation on those eggs. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithEggServers(int $id): Nest { @@ -85,7 +75,6 @@ public function getWithEggServers(int $id): Nest throw new RecordNotFoundException(); } - /* @var Nest $instance */ - return $instance; + return $instance; // @phpstan-ignore return.type } } diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 5d1a8bcac9..fe019e50a9 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -10,10 +10,8 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Node::class; } @@ -105,7 +103,7 @@ public function loadNodeAllocations(Node $node, bool $refresh = false): Node $node->allocations() ->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL') ->orderByRaw('INET_ATON(ip) ASC') - ->orderBy('port', 'asc') + ->orderBy('port') ->with('server:id,name') ->paginate(50) ); diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php index bcd9714089..603829bc1a 100644 --- a/app/Repositories/Eloquent/PermissionRepository.php +++ b/app/Repositories/Eloquent/PermissionRepository.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Repositories\Eloquent; -use Exception; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; class PermissionRepository extends EloquentRepository implements PermissionRepositoryInterface @@ -10,12 +9,10 @@ class PermissionRepository extends EloquentRepository implements PermissionRepos /** * Return the model backing this repository. * - * @return string - * * @throws \Exception */ - public function model() + public function model(): string { - throw new Exception('This functionality is not implemented.'); + throw new \Exception('This functionality is not implemented.'); } } diff --git a/app/Repositories/Eloquent/RecoveryTokenRepository.php b/app/Repositories/Eloquent/RecoveryTokenRepository.php index 5dfeeacfe2..5d4dc48501 100644 --- a/app/Repositories/Eloquent/RecoveryTokenRepository.php +++ b/app/Repositories/Eloquent/RecoveryTokenRepository.php @@ -6,10 +6,7 @@ class RecoveryTokenRepository extends EloquentRepository { - /** - * @return string - */ - public function model() + public function model(): string { return RecoveryToken::class; } diff --git a/app/Repositories/Eloquent/ScheduleRepository.php b/app/Repositories/Eloquent/ScheduleRepository.php index 5c999df87c..cff1c1144f 100644 --- a/app/Repositories/Eloquent/ScheduleRepository.php +++ b/app/Repositories/Eloquent/ScheduleRepository.php @@ -12,16 +12,14 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Schedule::class; } /** - * Return all of the schedules for a given server. + * Return all the schedules for a given server. */ public function findServerSchedules(int $server): Collection { @@ -29,15 +27,15 @@ public function findServerSchedules(int $server): Collection } /** - * Return a schedule model with all of the associated tasks as a relationship. + * Return a schedule model with all the associated tasks as a relationship. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getScheduleWithTasks(int $schedule): Schedule { try { return $this->getBuilder()->with('tasks')->findOrFail($schedule, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 8bb79c10f5..56097f9763 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -14,10 +14,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Server::class; } @@ -37,7 +35,7 @@ public function loadEggRelations(Server $server, bool $refresh = false): Server /** * Return a collection of servers with their associated data for rebuild operations. */ - public function getDataForRebuild(int $server = null, int $node = null): Collection + public function getDataForRebuild(?int $server = null, ?int $node = null): Collection { $instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']); @@ -53,7 +51,7 @@ public function getDataForRebuild(int $server = null, int $node = null): Collect /** * Return a collection of servers with their associated data for reinstall operations. */ - public function getDataForReinstall(int $server = null, int $node = null): Collection + public function getDataForReinstall(?int $server = null, ?int $node = null): Collection { $instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']); @@ -69,7 +67,7 @@ public function getDataForReinstall(int $server = null, int $node = null): Colle /** * Return a server model and all variables associated with the server. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function findWithVariables(int $id): Server { @@ -77,7 +75,7 @@ public function findWithVariables(int $id): Server return $this->getBuilder()->with('egg.variables', 'variables') ->where($this->getModel()->getKeyName(), '=', $id) ->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -141,12 +139,12 @@ public function getDaemonServiceData(Server $server, bool $refresh = false): arr /** * Return a server by UUID. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getByUuid(string $uuid): Server { try { - /** @var \Pterodactyl\Models\Server $model */ + /** @var Server $model */ $model = $this->getBuilder() ->with('nest', 'node') ->where(function (Builder $query) use ($uuid) { @@ -155,7 +153,7 @@ public function getByUuid(string $uuid): Server ->firstOrFail($this->getColumns()); return $model; - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -169,7 +167,7 @@ public function isUniqueUuidCombo(string $uuid, string $short): bool } /** - * Returns all of the servers that exist for a given node in a paginated response. + * Returns all the servers that exist for a given node in a paginated response. */ public function loadAllServersForNode(int $node, int $limit): LengthAwarePaginator { diff --git a/app/Repositories/Eloquent/ServerVariableRepository.php b/app/Repositories/Eloquent/ServerVariableRepository.php index d0d5e4dba1..26ded2cf64 100644 --- a/app/Repositories/Eloquent/ServerVariableRepository.php +++ b/app/Repositories/Eloquent/ServerVariableRepository.php @@ -9,10 +9,8 @@ class ServerVariableRepository extends EloquentRepository implements ServerVaria { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return ServerVariable::class; } diff --git a/app/Repositories/Eloquent/SessionRepository.php b/app/Repositories/Eloquent/SessionRepository.php index ad069abb82..5aa355dc04 100644 --- a/app/Repositories/Eloquent/SessionRepository.php +++ b/app/Repositories/Eloquent/SessionRepository.php @@ -10,16 +10,14 @@ class SessionRepository extends EloquentRepository implements SessionRepositoryI { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Session::class; } /** - * Return all of the active sessions for a user. + * Return all the active sessions for a user. */ public function getUserSessions(int $user): Collection { @@ -28,10 +26,8 @@ public function getUserSessions(int $user): Collection /** * Delete a session for a given user. - * - * @return int|null */ - public function deleteUserSession(int $user, string $session) + public function deleteUserSession(int $user, string $session): ?int { return $this->getBuilder()->where('user_id', $user)->where('id', $session)->delete(); } diff --git a/app/Repositories/Eloquent/SettingsRepository.php b/app/Repositories/Eloquent/SettingsRepository.php index b39d3a2e14..4deea80a70 100644 --- a/app/Repositories/Eloquent/SettingsRepository.php +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -7,22 +7,14 @@ class SettingsRepository extends EloquentRepository implements SettingsRepositoryInterface { - /** - * @var array - */ - private static $cache = []; + private static array $cache = []; - /** - * @var array - */ - private static $databaseMiss = []; + private static array $databaseMiss = []; /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Setting::class; } @@ -31,9 +23,8 @@ public function model() * Store a new persistent setting in the database. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function set(string $key, string $value = null) + public function set(string $key, ?string $value = null) { // Clear item from the cache. $this->clearCache($key); @@ -44,12 +35,8 @@ public function set(string $key, string $value = null) /** * Retrieve a persistent setting from the database. - * - * @param mixed $default - * - * @return mixed */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { // If item has already been requested return it from the cache. If // we already know it is missing, immediately return the default value. diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index d40ed9fda3..01bc0c0cb9 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -10,10 +10,8 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Subuser::class; } @@ -53,7 +51,7 @@ public function getWithPermissions(Subuser $subuser, bool $refresh = false): Sub /** * Return a subuser and associated permissions given a user_id and server_id. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getWithPermissionsUsingUserAndServer(int $user, int $server): Subuser { diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php index a375939adb..4a68167cfd 100644 --- a/app/Repositories/Eloquent/TaskRepository.php +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -11,10 +11,8 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Task::class; } @@ -22,26 +20,24 @@ public function model() /** * Get a task and the server relationship for that task. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws RecordNotFoundException */ public function getTaskForJobProcess(int $id): Task { try { return $this->getBuilder()->with('server.user', 'schedule')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } /** * Returns the next task in a schedule. - * - * @return \Pterodactyl\Models\Task|null */ - public function getNextTask(int $schedule, int $index) + public function getNextTask(int $schedule, int $index): ?Task { return $this->getBuilder()->where('schedule_id', '=', $schedule) - ->orderBy('sequence_id', 'asc') + ->orderBy('sequence_id') ->where('sequence_id', '>', $index) ->first($this->getColumns()); } diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index 72a88efb01..4445346251 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -9,10 +9,8 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return User::class; } diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php index a800519429..3cc0863dd2 100644 --- a/app/Repositories/Repository.php +++ b/app/Repositories/Repository.php @@ -2,55 +2,35 @@ namespace Pterodactyl\Repositories; -use InvalidArgumentException; use Illuminate\Foundation\Application; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Contracts\Repository\RepositoryInterface; abstract class Repository implements RepositoryInterface { - /** - * @var \Illuminate\Foundation\Application - */ - protected $app; - - /** - * @var array - */ - protected $columns = ['*']; + protected array $columns = ['*']; - /** - * @var mixed - */ - protected $model; + protected Model $model; - /** - * @var bool - */ - protected $withFresh = true; + protected bool $withFresh = true; /** * Repository constructor. */ - public function __construct(Application $application) + public function __construct(protected Application $app) { - $this->app = $application; - $this->initializeModel($this->model()); } /** * Return the model backing this repository. - * - * @return string|\Closure|object */ - abstract public function model(); + abstract public function model(): string; /** * Return the model being used for this repository. - * - * @return mixed */ - public function getModel() + public function getModel(): Model { return $this->model; } @@ -59,10 +39,8 @@ public function getModel() * Setup column selection functionality. * * @param array|string $columns - * - * @return $this */ - public function setColumns($columns = ['*']) + public function setColumns($columns = ['*']): self { $clone = clone $this; $clone->columns = is_array($columns) ? $columns : func_get_args(); @@ -72,10 +50,8 @@ public function setColumns($columns = ['*']) /** * Return the columns to be selected in the repository call. - * - * @return array */ - public function getColumns() + public function getColumns(): array { return $this->columns; } @@ -83,31 +59,25 @@ public function getColumns() /** * Stop repository update functions from returning a fresh * model when changes are committed. - * - * @return $this */ - public function withoutFreshModel() + public function withoutFreshModel(): self { return $this->setFreshModel(false); } /** * Return a fresh model with a repository updates a model. - * - * @return $this */ - public function withFreshModel() + public function withFreshModel(): self { - return $this->setFreshModel(true); + return $this->setFreshModel(); } /** - * Set whether or not the repository should return a fresh model + * Set whether the repository should return a fresh model * when changes are committed. - * - * @return $this */ - public function setFreshModel(bool $fresh = true) + public function setFreshModel(bool $fresh = true): self { $clone = clone $this; $clone->withFresh = $fresh; @@ -117,12 +87,8 @@ public function setFreshModel(bool $fresh = true) /** * Take the provided model and make it accessible to the rest of the repository. - * - * @param array $model - * - * @return mixed */ - protected function initializeModel(...$model) + protected function initializeModel(string ...$model): mixed { switch (count($model)) { case 1: @@ -130,7 +96,7 @@ protected function initializeModel(...$model) case 2: return $this->model = call_user_func([$this->app->make($model[0]), $model[1]]); default: - throw new InvalidArgumentException('Model must be a FQDN or an array with a count of two.'); + throw new \InvalidArgumentException('Model must be a FQDN or an array with a count of two.'); } } } diff --git a/app/Repositories/Wings/DaemonBackupRepository.php b/app/Repositories/Wings/DaemonBackupRepository.php index 571775fa52..2287840897 100644 --- a/app/Repositories/Wings/DaemonBackupRepository.php +++ b/app/Repositories/Wings/DaemonBackupRepository.php @@ -9,19 +9,18 @@ use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonBackupRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonBackupRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonBackupRepository extends DaemonRepository { - /** - * @var string|null - */ - protected $adapter; + protected ?string $adapter; /** * Sets the backup adapter for this execution instance. - * - * @return $this */ - public function setBackupAdapter(string $adapter) + public function setBackupAdapter(string $adapter): self { $this->adapter = $adapter; @@ -31,7 +30,7 @@ public function setBackupAdapter(string $adapter) /** * Tells the remote Daemon to begin generating a backup for the server. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function backup(Backup $backup): ResponseInterface { @@ -56,9 +55,9 @@ public function backup(Backup $backup): ResponseInterface /** * Sends a request to Wings to begin restoring a backup for a server. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function restore(Backup $backup, string $url = null, bool $truncate = false): ResponseInterface + public function restore(Backup $backup, ?string $url = null, bool $truncate = false): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); @@ -81,7 +80,7 @@ public function restore(Backup $backup, string $url = null, bool $truncate = fal /** * Deletes a backup from the daemon. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function delete(Backup $backup): ResponseInterface { diff --git a/app/Repositories/Wings/DaemonCommandRepository.php b/app/Repositories/Wings/DaemonCommandRepository.php index 390434bff6..858d41a773 100644 --- a/app/Repositories/Wings/DaemonCommandRepository.php +++ b/app/Repositories/Wings/DaemonCommandRepository.php @@ -8,16 +8,18 @@ use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonCommandRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonCommandRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonCommandRepository extends DaemonRepository { /** * Sends a command or multiple commands to a running server instance. * - * @param string|string[] $command - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function send($command): ResponseInterface + public function send(array|string $command): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php index f2672ddfa6..90a7ef21ce 100644 --- a/app/Repositories/Wings/DaemonConfigurationRepository.php +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -3,20 +3,25 @@ namespace Pterodactyl\Repositories\Wings; use Pterodactyl\Models\Node; +use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonConfigurationRepository extends DaemonRepository { /** * Returns system information from the wings instance. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function getSystemInformation(): array + public function getSystemInformation(?int $version = null): array { try { - $response = $this->getHttpClient()->get('/api/system'); + $response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : '')); } catch (TransferException $exception) { throw new DaemonConnectionException($exception); } @@ -29,11 +34,9 @@ public function getSystemInformation(): array * this instance using a passed-in model. This allows us to change plenty of information * in the model, and still use the old, pre-update model to actually make the HTTP request. * - * @return \Psr\Http\Message\ResponseInterface - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function update(Node $node) + public function update(Node $node): ResponseInterface { try { return $this->getHttpClient()->post( diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php index f1acdc7de6..af8db9be61 100644 --- a/app/Repositories/Wings/DaemonFileRepository.php +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -2,13 +2,19 @@ namespace Pterodactyl\Repositories\Wings; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Psr\Http\Message\ResponseInterface; +use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonFileRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonFileRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonFileRepository extends DaemonRepository { /** @@ -16,11 +22,11 @@ class DaemonFileRepository extends DaemonRepository * * @param int|null $notLargerThan the maximum content length in bytes * - * @throws \GuzzleHttp\Exception\TransferException - * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws TransferException + * @throws FileSizeTooLargeException + * @throws DaemonConnectionException */ - public function getContent(string $path, int $notLargerThan = null): string + public function getContent(string $path, ?int $notLargerThan = null): string { Assert::isInstanceOf($this->server, Server::class); @@ -31,12 +37,11 @@ public function getContent(string $path, int $notLargerThan = null): string 'query' => ['file' => $path], ] ); - } catch (TransferException $exception) { + } catch (ClientException|TransferException $exception) { throw new DaemonConnectionException($exception); } - $length = (int) $response->getHeader('Content-Length')[0] ?? 0; - + $length = (int) Arr::get($response->getHeader('Content-Length'), 0, 0); if ($notLargerThan && $length > $notLargerThan) { throw new FileSizeTooLargeException(); } @@ -48,7 +53,7 @@ public function getContent(string $path, int $notLargerThan = null): string * Save new contents to a given file. This works for both creating and updating * a file. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function putContent(string $path, string $content): ResponseInterface { @@ -70,7 +75,7 @@ public function putContent(string $path, string $content): ResponseInterface /** * Return a directory listing for a given path. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function getDirectory(string $path): array { @@ -93,7 +98,7 @@ public function getDirectory(string $path): array /** * Creates a new directory for the server in the given $path. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function createDirectory(string $name, string $path): ResponseInterface { @@ -117,7 +122,7 @@ public function createDirectory(string $name, string $path): ResponseInterface /** * Renames or moves a file on the remote machine. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function renameFiles(?string $root, array $files): ResponseInterface { @@ -141,7 +146,7 @@ public function renameFiles(?string $root, array $files): ResponseInterface /** * Copy a given file and give it a unique name. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function copyFile(string $location): ResponseInterface { @@ -164,7 +169,7 @@ public function copyFile(string $location): ResponseInterface /** * Delete a file or folder for the server. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function deleteFiles(?string $root, array $files): ResponseInterface { @@ -188,7 +193,7 @@ public function deleteFiles(?string $root, array $files): ResponseInterface /** * Compress the given files or folders in the given root. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function compressFiles(?string $root, array $files): array { @@ -217,7 +222,7 @@ public function compressFiles(?string $root, array $files): array /** * Decompresses a given archive file. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function decompressFile(?string $root, string $file): ResponseInterface { @@ -231,6 +236,9 @@ public function decompressFile(?string $root, string $file): ResponseInterface 'root' => $root ?? '/', 'file' => $file, ], + // Wait for up to 15 minutes for the decompress to be completed when calling this endpoint + // since it will likely take quite awhile for large directories. + 'timeout' => 60 * 15, ] ); } catch (TransferException $exception) { @@ -241,7 +249,7 @@ public function decompressFile(?string $root, string $file): ResponseInterface /** * Chmods the given files. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function chmodFiles(?string $root, array $files): ResponseInterface { @@ -265,17 +273,25 @@ public function chmodFiles(?string $root, array $files): ResponseInterface /** * Pulls a file from the given URL and saves it to the disk. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function pull(string $url, ?string $directory): ResponseInterface + public function pull(string $url, ?string $directory, array $params = []): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); + $attributes = [ + 'url' => $url, + 'root' => $directory ?? '/', + 'file_name' => $params['filename'] ?? null, + 'use_header' => $params['use_header'] ?? null, + 'foreground' => $params['foreground'] ?? null, + ]; + try { return $this->getHttpClient()->post( sprintf('/api/servers/%s/files/pull', $this->server->uuid), [ - 'json' => ['url' => $url, 'directory' => $directory ?? '/'], + 'json' => array_filter($attributes, fn ($value) => !is_null($value)), ] ); } catch (TransferException $exception) { diff --git a/app/Repositories/Wings/DaemonPowerRepository.php b/app/Repositories/Wings/DaemonPowerRepository.php index 4e290cfc91..2b6f339a6d 100644 --- a/app/Repositories/Wings/DaemonPowerRepository.php +++ b/app/Repositories/Wings/DaemonPowerRepository.php @@ -8,12 +8,16 @@ use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonPowerRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonPowerRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonPowerRepository extends DaemonRepository { /** * Sends a power action to the server instance. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function send(string $action): ResponseInterface { diff --git a/app/Repositories/Wings/DaemonRepository.php b/app/Repositories/Wings/DaemonRepository.php index 268a4a2131..512626d73f 100644 --- a/app/Repositories/Wings/DaemonRepository.php +++ b/app/Repositories/Wings/DaemonRepository.php @@ -10,35 +10,21 @@ abstract class DaemonRepository { - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - protected $app; + protected ?Server $server; - /** - * @var \Pterodactyl\Models\Server|null - */ - protected $server; - - /** - * @var \Pterodactyl\Models\Node|null - */ - protected $node; + protected ?Node $node; /** - * BaseWingsRepository constructor. + * DaemonRepository constructor. */ - public function __construct(Application $application) + public function __construct(protected Application $app) { - $this->app = $application; } /** * Set the server model this request is stemming from. - * - * @return $this */ - public function setServer(Server $server) + public function setServer(Server $server): self { $this->server = $server; @@ -49,10 +35,8 @@ public function setServer(Server $server) /** * Set the node model this request is stemming from. - * - * @return $this */ - public function setNode(Node $node) + public function setNode(Node $node): self { $this->node = $node; diff --git a/app/Repositories/Wings/DaemonRevocationRepository.php b/app/Repositories/Wings/DaemonRevocationRepository.php new file mode 100644 index 0000000000..f8a839985d --- /dev/null +++ b/app/Repositories/Wings/DaemonRevocationRepository.php @@ -0,0 +1,27 @@ +getHttpClient()->post('/api/deauthorize-user', [ + 'json' => ['user' => $user, 'servers' => $servers], + ]); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonServerRepository.php b/app/Repositories/Wings/DaemonServerRepository.php index f013ac08c4..07d40cceb4 100644 --- a/app/Repositories/Wings/DaemonServerRepository.php +++ b/app/Repositories/Wings/DaemonServerRepository.php @@ -8,12 +8,16 @@ use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonServerRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonServerRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonServerRepository extends DaemonRepository { /** * Returns details about a server from the Daemon instance. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function getDetails(): array { @@ -33,7 +37,7 @@ public function getDetails(): array /** * Creates a new server on the Wings daemon. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function create(bool $startOnCompletion = true): void { @@ -54,7 +58,7 @@ public function create(bool $startOnCompletion = true): void /** * Triggers a server sync on Wings. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function sync(): void { @@ -70,7 +74,7 @@ public function sync(): void /** * Delete a server from the daemon, forcibly if passed. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function delete(): void { @@ -86,7 +90,7 @@ public function delete(): void /** * Reinstall a server on the daemon. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function reinstall(): void { @@ -106,7 +110,7 @@ public function reinstall(): void * Requests the daemon to create a full archive of the server. Once the daemon is finished * they will send a POST request to "/api/remote/servers/{uuid}/archive" with a boolean. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ public function requestArchive(): void { @@ -127,7 +131,10 @@ public function requestArchive(): void * make it easier to revoke tokens on the fly. This ensures that the JTI key is formatted * correctly and avoids any costly mistakes in the codebase. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @deprecated + * @see \Pterodactyl\Repositories\Wings\DaemonRevocationRepository::deauthorize() + * + * @throws DaemonConnectionException */ public function revokeUserJTI(int $id): void { @@ -140,7 +147,7 @@ public function revokeUserJTI(int $id): void * Revokes an array of JWT JTI's by marking any token generated before the current time on * the Wings instance as being invalid. * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ protected function revokeJTIs(array $jtis): void { diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php index 3939a47cd1..9a2cd2160a 100644 --- a/app/Repositories/Wings/DaemonTransferRepository.php +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -2,26 +2,30 @@ namespace Pterodactyl\Repositories\Wings; +use Pterodactyl\Models\Node; use Lcobucci\JWT\Token\Plain; -use Pterodactyl\Models\Server; use GuzzleHttp\Exception\GuzzleException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +/** + * @method \Pterodactyl\Repositories\Wings\DaemonTransferRepository setNode(\Pterodactyl\Models\Node $node) + * @method \Pterodactyl\Repositories\Wings\DaemonTransferRepository setServer(\Pterodactyl\Models\Server $server) + */ class DaemonTransferRepository extends DaemonRepository { /** - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws DaemonConnectionException */ - public function notify(Server $server, Plain $token): void + public function notify(Node $targetNode, Plain $token): void { try { - $this->getHttpClient()->post('/api/transfer', [ + $this->getHttpClient()->post(sprintf('/api/servers/%s/transfer', $this->server->uuid), [ 'json' => [ - 'server_id' => $server->uuid, - 'url' => $server->node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), + 'server_id' => $this->server->uuid, + 'url' => $targetNode->getConnectionAddress() . '/api/transfers', 'token' => 'Bearer ' . $token->toString(), 'server' => [ - 'uuid' => $server->uuid, + 'uuid' => $this->server->uuid, 'start_on_completion' => false, ], ], diff --git a/app/Rules/Fqdn.php b/app/Rules/Fqdn.php new file mode 100644 index 0000000000..19a500963d --- /dev/null +++ b/app/Rules/Fqdn.php @@ -0,0 +1,79 @@ +data = $data; + + return $this; + } + + /** + * Validates that the value provided resolves to an IP address. If a scheme is + * specified when this rule is created additional checks will be applied. + * + * @param string $attribute + */ + public function passes($attribute, $value): bool + { + if (filter_var($value, FILTER_VALIDATE_IP)) { + // Check if the scheme is set to HTTPS. + // + // Unless someone owns their IP blocks and decides to pay who knows how much for a + // custom SSL cert, IPs will not be able to use HTTPS. This should prevent most + // home users from making this mistake and wondering why their node is not working. + if ($this->schemeField && Arr::get($this->data, $this->schemeField) === 'https') { + $this->message = 'The :attribute must not be an IP address when HTTPS is enabled.'; + + return false; + } + + return true; + } + + // Lookup A and AAAA DNS records for the FQDN. Note, this function will also resolve CNAMEs + // for us automatically, there is no need to manually resolve them here. + // + // The error suppression is intentional, see https://bugs.php.net/bug.php?id=73149 + $records = @dns_get_record($value, DNS_A + DNS_AAAA); + // If no records were returned fall back to trying to resolve the value using the hosts DNS + // resolution. This will not work for IPv6 which is why we prefer to use `dns_get_record` + // first. + if (!empty($records) || filter_var(gethostbyname($value), FILTER_VALIDATE_IP)) { + return true; + } + + $this->message = 'The :attribute could not be resolved to a valid IP address.'; + + return false; + } + + public function message(): string + { + return $this->message; + } + + /** + * Returns a new instance of the rule with a defined scheme set. + */ + public static function make(?string $schemeField = null): self + { + return tap(new self(), function ($fqdn) use ($schemeField) { + $fqdn->schemeField = $schemeField; + }); + } +} diff --git a/app/Rules/Username.php b/app/Rules/Username.php index bae204952b..4e3dc249cd 100644 --- a/app/Rules/Username.php +++ b/app/Rules/Username.php @@ -13,12 +13,11 @@ class Username implements Rule /** * Validate that a username contains only the allowed characters and starts/ends - * with alpha-numeric characters. + * with alphanumeric characters. * * Allowed characters: a-z0-9_-. * * @param string $attribute - * @param mixed $value */ public function passes($attribute, $value): bool { @@ -37,10 +36,8 @@ public function message(): string /** * Convert the rule to a validation string. This is necessary to avoid * issues with Eloquence which tries to use this rule as a string. - * - * @return string */ - public function __toString() + public function __toString(): string { return 'p_username'; } diff --git a/app/Services/Acl/Api/AdminAcl.php b/app/Services/Acl/Api/AdminAcl.php index 880794adca..cee076e174 100644 --- a/app/Services/Acl/Api/AdminAcl.php +++ b/app/Services/Acl/Api/AdminAcl.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Services\Acl\Api; -use ReflectionClass; use Pterodactyl\Models\ApiKey; class AdminAcl @@ -37,10 +36,8 @@ class AdminAcl /** * Determine if an API key has permission to perform a specific read/write operation. - * - * @return bool */ - public static function can(int $permission, int $action = self::READ) + public static function can(int $permission, int $action = self::READ): bool { if ($permission & $action) { return true; @@ -52,10 +49,8 @@ public static function can(int $permission, int $action = self::READ) /** * Determine if an API Key model has permission to access a given resource * at a specific action level. - * - * @return bool */ - public static function check(ApiKey $key, string $resource, int $action = self::READ) + public static function check(ApiKey $key, string $resource, int $action = self::READ): bool { return self::can(data_get($key, self::COLUMN_IDENTIFIER . $resource, self::NONE), $action); } @@ -67,7 +62,7 @@ public static function check(ApiKey $key, string $resource, int $action = self:: */ public static function getResourceList(): array { - $reflect = new ReflectionClass(__CLASS__); + $reflect = new \ReflectionClass(__CLASS__); return collect($reflect->getConstants())->filter(function ($value, $key) { return substr($key, 0, 9) === 'RESOURCE_'; diff --git a/app/Services/Activity/ActivityLogBatchService.php b/app/Services/Activity/ActivityLogBatchService.php new file mode 100644 index 0000000000..f4206ea569 --- /dev/null +++ b/app/Services/Activity/ActivityLogBatchService.php @@ -0,0 +1,59 @@ +uuid; + } + + /** + * Starts a new batch transaction. If there is already a transaction present + * this will be nested. + */ + public function start(): void + { + if ($this->transaction === 0) { + $this->uuid = Uuid::uuid4()->toString(); + } + + ++$this->transaction; + } + + /** + * Ends a batch transaction, if this is the last transaction in the stack + * the UUID will be cleared out. + */ + public function end(): void + { + $this->transaction = max(0, $this->transaction - 1); + + if ($this->transaction === 0) { + $this->uuid = null; + } + } + + /** + * Executes the logic provided within the callback in the scope of an activity + * log batch transaction. + */ + public function transaction(\Closure $callback): mixed + { + $this->start(); + $result = $callback($this->uuid()); + $this->end(); + + return $result; + } +} diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php new file mode 100644 index 0000000000..4fb687905d --- /dev/null +++ b/app/Services/Activity/ActivityLogService.php @@ -0,0 +1,253 @@ +getActivity()->actor_id = null; + $this->getActivity()->actor_type = null; + $this->getActivity()->setRelation('actor', null); + + return $this; + } + + /** + * Sets the action for this activity log. + */ + public function event(string $action): self + { + $this->getActivity()->event = $action; + + return $this; + } + + /** + * Set the description for this activity. + */ + public function description(?string $description): self + { + $this->getActivity()->description = $description; + + return $this; + } + + /** + * Sets the subject model instance. + * + * @template T extends \Illuminate\Database\Eloquent\Model|\Illuminate\Contracts\Auth\Authenticatable + * + * @param T|T[]|null $subjects + */ + public function subject(...$subjects): self + { + foreach (Arr::wrap($subjects) as $subject) { + if (is_null($subject)) { + continue; + } + + foreach ($this->subjects as $entry) { + // If this subject is already tracked in our array of subjects just skip over + // it and move on to the next one in the list. + if ($entry->is($subject)) { + continue 2; + } + } + + $this->subjects[] = $subject; + } + + return $this; + } + + /** + * Sets the actor model instance. + */ + public function actor(Model $actor): self + { + $this->getActivity()->actor()->associate($actor); + + return $this; + } + + /** + * Sets a custom property on the activity log instance. + * + * @param string|array $key + */ + public function property($key, $value = null): self + { + $properties = $this->getActivity()->properties; + $this->activity->properties = is_array($key) + ? $properties->merge($key) + : $properties->put($key, $value); + + return $this; + } + + /** + * Attaches the instance request metadata to the activity log event. + */ + public function withRequestMetadata(): self + { + return $this->property([ + 'ip' => Request::getClientIp(), + 'useragent' => Request::userAgent(), + ]); + } + + /** + * Logs an activity log entry with the set values and then returns the + * model instance to the caller. If there is an exception encountered while + * performing this action it will be logged to the disk but will not interrupt + * the code flow. + */ + public function log(?string $description = null): ActivityLog + { + $activity = $this->getActivity(); + + if (!is_null($description)) { + $activity->description = $description; + } + + try { + return $this->save(); + } catch (\Throwable $exception) { + if (config('app.env') !== 'production') { + /* @noinspection PhpUnhandledExceptionInspection */ + throw $exception; + } + + Log::error($exception); + } + + return $activity; + } + + /** + * Returns a cloned instance of the service allowing for the creation of a base + * activity log with the ability to change values on the fly without impact. + */ + public function clone(): self + { + return clone $this; + } + + /** + * Executes the provided callback within the scope of a database transaction + * and will only save the activity log entry if everything else successfully + * settles. + * + * @param \Closure($this): mixed $callback + * + * @throws \Throwable + */ + public function transaction(\Closure $callback) + { + return $this->connection->transaction(function () use ($callback) { + $response = $callback($this); + + $this->save(); + + return $response; + }); + } + + /** + * Resets the instance and clears out the log. + */ + public function reset(): void + { + $this->activity = null; + $this->subjects = []; + } + + /** + * Returns the current activity log instance. + */ + protected function getActivity(): ActivityLog + { + if ($this->activity) { + return $this->activity; + } + + $this->activity = new ActivityLog([ + 'ip' => Request::ip(), + 'batch' => $this->batch->uuid(), + 'properties' => Collection::make([]), + 'api_key_id' => $this->targetable->apiKeyId(), + ]); + + if ($subject = $this->targetable->subject()) { + $this->subject($subject); + } + + if ($actor = $this->targetable->actor()) { + $this->actor($actor); + } elseif (! is_null($user = $this->manager->guard()->user())) { + $this->actor($user); + } + + return $this->activity; + } + + /** + * Saves the activity log instance and attaches all of the subject models. + * + * @throws \Throwable + */ + protected function save(): ActivityLog + { + Assert::notNull($this->activity); + + $response = $this->connection->transaction(function () { + $this->activity->save(); + + $subjects = Collection::make($this->subjects) + ->map(fn (Model $subject) => [ + 'activity_log_id' => $this->activity->id, + 'subject_id' => $subject->getKey(), + 'subject_type' => $subject->getMorphClass(), + ]) + ->values() + ->toArray(); + + ActivityLogSubject::insert($subjects); + + return $this->activity; + }); + + $this->activity = null; + $this->subjects = []; + + return $response; + } +} diff --git a/app/Services/Activity/ActivityLogTargetableService.php b/app/Services/Activity/ActivityLogTargetableService.php new file mode 100644 index 0000000000..4d37e98288 --- /dev/null +++ b/app/Services/Activity/ActivityLogTargetableService.php @@ -0,0 +1,51 @@ +actor = $actor; + } + + public function setSubject(Model $subject): void + { + $this->subject = $subject; + } + + public function setApiKeyId(?int $apiKeyId): void + { + $this->apiKeyId = $apiKeyId; + } + + public function actor(): ?Model + { + return $this->actor; + } + + public function subject(): ?Model + { + return $this->subject; + } + + public function apiKeyId(): ?int + { + return $this->apiKeyId; + } + + public function reset(): void + { + $this->actor = null; + $this->subject = null; + $this->apiKeyId = null; + } +} diff --git a/app/Services/Allocations/AllocationDeletionService.php b/app/Services/Allocations/AllocationDeletionService.php index 4b1bfec3b2..be5d4372fa 100644 --- a/app/Services/Allocations/AllocationDeletionService.php +++ b/app/Services/Allocations/AllocationDeletionService.php @@ -8,28 +8,20 @@ class AllocationDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $repository; - /** * AllocationDeletionService constructor. */ - public function __construct(AllocationRepositoryInterface $repository) + public function __construct(private AllocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Delete an allocation from the database only if it does not have a server * that is actively attached to it. * - * @return int - * - * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException + * @throws ServerUsingAllocationException */ - public function handle(Allocation $allocation) + public function handle(Allocation $allocation): int { if (!is_null($allocation->server_id)) { throw new ServerUsingAllocationException(trans('exceptions.allocations.server_using')); diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 878c7307ad..94f347c6c0 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Services\Allocations; -use Exception; use IPTools\Network; use Pterodactyl\Models\Node; use Illuminate\Database\ConnectionInterface; @@ -15,42 +14,30 @@ class AssignmentService { - public const CIDR_MAX_BITS = 27; + public const CIDR_MAX_BITS = 25; public const CIDR_MIN_BITS = 32; public const PORT_FLOOR = 1024; public const PORT_CEIL = 65535; public const PORT_RANGE_LIMIT = 1000; public const PORT_RANGE_REGEX = '/^(\d{4,5})-(\d{4,5})$/'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $repository; - /** * AssignmentService constructor. */ - public function __construct(AllocationRepositoryInterface $repository, ConnectionInterface $connection) + public function __construct(protected AllocationRepositoryInterface $repository, protected ConnectionInterface $connection) { - $this->connection = $connection; - $this->repository = $repository; } /** * Insert allocations into the database and link them to a specific node. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException - * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException - * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException - * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException + * @throws DisplayException + * @throws CidrOutOfRangeException + * @throws InvalidPortMappingException + * @throws PortOutOfRangeException + * @throws TooManyPortsInRangeException */ - public function handle(Node $node, array $data) + public function handle(Node $node, array $data): void { $explode = explode('/', $data['allocation_ip']); if (count($explode) !== 1) { @@ -60,10 +47,14 @@ public function handle(Node $node, array $data) } try { + // TODO: how should we approach supporting IPv6 with this? + // gethostbyname only supports IPv4, but the alternative (dns_get_record) returns + // an array of records, which is not ideal for this use case, we need a SINGLE + // IP to use, not multiple. $underlying = gethostbyname($data['allocation_ip']); $parsed = Network::parse($underlying); - } catch (Exception $exception) { - /* @noinspection PhpUndefinedVariableInspection */ + } catch (\Exception $exception) { + // @phpstan-ignore-next-line variable.undefined throw new DisplayException("Could not parse provided allocation IP address ({$underlying}): {$exception->getMessage()}", $exception); } diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index 29c37123f8..76b27ac6b1 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -10,19 +10,11 @@ class FindAssignableAllocationService { - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - private $service; - /** * FindAssignableAllocationService constructor. - * - * @param \Pterodactyl\Services\Allocations\AssignmentService $service */ - public function __construct(AssignmentService $service) + public function __construct(private AssignmentService $service) { - $this->service = $service; } /** @@ -30,15 +22,13 @@ public function __construct(AssignmentService $service) * no allocation can be found, a new one will be created with a random port between the defined * range from the configuration. * - * @return \Pterodactyl\Models\Allocation - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function handle(Server $server) + public function handle(Server $server): Allocation { if (!config('pterodactyl.client_features.allocations.enabled')) { throw new AutoAllocationNotEnabledException(); @@ -47,8 +37,9 @@ public function handle(Server $server) // Attempt to find a given available allocation for a server. If one cannot be found // we will fall back to attempting to create a new allocation that can be used for the // server. - /** @var \Pterodactyl\Models\Allocation|null $allocation */ + /** @var Allocation|null $allocation */ $allocation = $server->node->allocations() + ->lockForUpdate() ->where('ip', $server->allocation->ip) ->whereNull('server_id') ->inRandomOrder() @@ -110,8 +101,9 @@ protected function createNewAllocation(Server $server): Allocation 'allocation_ports' => [$port], ]); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = $server->node->allocations() + ->lockForUpdate() ->where('ip', $server->allocation->ip) ->where('port', $port) ->firstOrFail(); diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php index 20a11add02..f026b9f221 100644 --- a/app/Services/Api/KeyCreationService.php +++ b/app/Services/Api/KeyCreationService.php @@ -8,37 +8,20 @@ class KeyCreationService { - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var int - */ - private $keyType = ApiKey::TYPE_NONE; - - /** - * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface - */ - private $repository; + private int $keyType = ApiKey::TYPE_NONE; /** * ApiKeyService constructor. */ - public function __construct(ApiKeyRepositoryInterface $repository, Encrypter $encrypter) + public function __construct(private ApiKeyRepositoryInterface $repository, private Encrypter $encrypter) { - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Set the type of key that should be created. By default an orphaned key will be * created. These keys cannot be used for anything, and will not render in the UI. - * - * @return \Pterodactyl\Services\Api\KeyCreationService */ - public function setKeyType(int $type) + public function setKeyType(int $type): self { $this->keyType = $type; @@ -56,7 +39,7 @@ public function handle(array $data, array $permissions = []): ApiKey { $data = array_merge($data, [ 'key_type' => $this->keyType, - 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), + 'identifier' => ApiKey::generateTokenIdentifier($this->keyType), 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), ]); diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index 66eefe6755..ce7cdfcd25 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -7,46 +7,17 @@ use GuzzleHttp\Exception\ClientException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Extensions\Backups\BackupManager; -use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Exceptions\Service\Backup\BackupLockedException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class DeleteBackupService { - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository - */ - private $daemonBackupRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $manager; - - /** - * DeleteBackupService constructor. - */ public function __construct( - ConnectionInterface $connection, - BackupRepository $repository, - BackupManager $manager, - DaemonBackupRepository $daemonBackupRepository + private ConnectionInterface $connection, + private BackupManager $manager, + private DaemonBackupRepository $daemonBackupRepository, ) { - $this->repository = $repository; - $this->daemonBackupRepository = $daemonBackupRepository; - $this->connection = $connection; - $this->manager = $manager; } /** @@ -55,7 +26,7 @@ public function __construct( * * @throws \Throwable */ - public function handle(Backup $backup) + public function handle(Backup $backup): void { // If the backup is marked as failed it can still be deleted, even if locked // since the UI doesn't allow you to unlock a failed backup in the first place. @@ -79,13 +50,13 @@ public function handle(Backup $backup) } catch (DaemonConnectionException $exception) { $previous = $exception->getPrevious(); // Don't fail the request if the Daemon responds with a 404, just assume the backup - // doesn't actually exist and remove it's reference from the Panel as well. + // doesn't actually exist and remove its reference from the Panel as well. if (!$previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) { throw $exception; } } - $this->repository->delete($backup->id); + $backup->delete(); }); } @@ -94,14 +65,15 @@ public function handle(Backup $backup) * * @throws \Throwable */ - protected function deleteFromS3(Backup $backup) + protected function deleteFromS3(Backup $backup): void { $this->connection->transaction(function () use ($backup) { - $this->repository->delete($backup->id); + $backup->delete(); - /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); + // @phpstan-ignore-next-line method.notFound $adapter->getClient()->deleteObject([ 'Bucket' => $adapter->getBucket(), 'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid), diff --git a/app/Services/Backups/DownloadLinkService.php b/app/Services/Backups/DownloadLinkService.php index 7d5af4ca01..f27d747c3b 100644 --- a/app/Services/Backups/DownloadLinkService.php +++ b/app/Services/Backups/DownloadLinkService.php @@ -4,29 +4,18 @@ use Carbon\CarbonImmutable; use Pterodactyl\Models\User; +use Pterodactyl\Enum\JwtScope; use Pterodactyl\Models\Backup; use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Extensions\Backups\BackupManager; class DownloadLinkService { - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * DownloadLinkService constructor. */ - public function __construct(BackupManager $backupManager, NodeJWTService $jwtService) + public function __construct(private BackupManager $backupManager, private NodeJWTService $jwtService) { - $this->backupManager = $backupManager; - $this->jwtService = $jwtService; } /** @@ -41,10 +30,12 @@ public function handle(Backup $backup, User $user): string $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setUser($user) ->setClaims([ 'backup_uuid' => $backup->uuid, 'server_uuid' => $backup->server->uuid, ]) + ->setScopes(JwtScope::BackupDownload) ->handle($backup->server->node, $user->id . $backup->server->uuid); return sprintf('%s/download/backup?token=%s', $backup->server->node->getConnectionAddress(), $token->toString()); @@ -53,12 +44,10 @@ public function handle(Backup $backup, User $user): string /** * Returns a signed URL that allows us to download a file directly out of a non-public * S3 bucket by using a signed URL. - * - * @return string */ - protected function getS3BackupUrl(Backup $backup) + protected function getS3BackupUrl(Backup $backup): string { - /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->backupManager->adapter(Backup::ADAPTER_AWS_S3); $request = $adapter->getClient()->createPresignedRequest( diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index 705bdf4280..a1ecd38888 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -16,65 +16,25 @@ class InitiateBackupService { - /** - * @var string[]|null - */ - private $ignoredFiles; - - /** - * @var bool - */ - private $isLocked = false; - - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; + private array $ignoredFiles = []; - /** - * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository - */ - private $daemonBackupRepository; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - - /** - * @var \Pterodactyl\Services\Backups\DeleteBackupService - */ - private $deleteBackupService; + private bool $isLocked = false; /** * InitiateBackupService constructor. - * - * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService */ public function __construct( - BackupRepository $repository, - ConnectionInterface $connection, - DaemonBackupRepository $daemonBackupRepository, - DeleteBackupService $deleteBackupService, - BackupManager $backupManager + private BackupRepository $repository, + private ConnectionInterface $connection, + private DaemonBackupRepository $daemonBackupRepository, + private DeleteBackupService $deleteBackupService, + private BackupManager $backupManager, ) { - $this->repository = $repository; - $this->connection = $connection; - $this->daemonBackupRepository = $daemonBackupRepository; - $this->backupManager = $backupManager; - $this->deleteBackupService = $deleteBackupService; } /** * Set if the backup should be locked once it is created which will prevent * its deletion by users or automated system processes. - * - * @return $this */ public function setIsLocked(bool $isLocked): self { @@ -87,14 +47,12 @@ public function setIsLocked(bool $isLocked): self * Sets the files to be ignored by this backup. * * @param string[]|null $ignored - * - * @return $this */ - public function setIgnoredFiles(?array $ignored) + public function setIgnoredFiles(?array $ignored): self { if (is_array($ignored)) { foreach ($ignored as $value) { - Assert::string($value); + Assert::string($value); // @phpstan-ignore staticMethod.alreadyNarrowedType } } @@ -112,10 +70,10 @@ public function setIgnoredFiles(?array $ignored) * Initiates the backup process for a server on Wings. * * @throws \Throwable - * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException - * @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException + * @throws TooManyBackupsException + * @throws TooManyRequestsHttpException */ - public function handle(Server $server, string $name = null, bool $override = false): Backup + public function handle(Server $server, ?string $name = null, bool $override = false): Backup { $limit = config('backups.throttles.limit'); $period = config('backups.throttles.period'); @@ -124,7 +82,7 @@ public function handle(Server $server, string $name = null, bool $override = fal if ($previous->count() >= $limit) { $message = sprintf('Only %d backups may be generated within a %d second span of time.', $limit, $period); - throw new TooManyRequestsHttpException(CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), $message); + throw new TooManyRequestsHttpException((int) CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), $message); } } @@ -140,7 +98,6 @@ public function handle(Server $server, string $name = null, bool $override = fal // Get the oldest backup the server has that is not "locked" (indicating a backup that should // never be automatically purged). If we find a backup we will delete it and then continue with // this process. If no backup is found that can be used an exception is thrown. - /** @var \Pterodactyl\Models\Backup $oldest */ $oldest = $successful->where('is_locked', false)->orderBy('created_at')->first(); if (!$oldest) { throw new TooManyBackupsException($server->backup_limit); @@ -150,12 +107,12 @@ public function handle(Server $server, string $name = null, bool $override = fal } return $this->connection->transaction(function () use ($server, $name) { - /** @var \Pterodactyl\Models\Backup $backup */ + /** @var Backup $backup */ $backup = $this->repository->create([ 'server_id' => $server->id, 'uuid' => Uuid::uuid4()->toString(), 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), - 'ignored_files' => array_values($this->ignoredFiles ?? []), + 'ignored_files' => array_values($this->ignoredFiles), 'disk' => $this->backupManager->getDefaultAdapter(), 'is_locked' => $this->isLocked, ], true, true); diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index a10b227196..1cabddbdb6 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Services\Databases; -use Exception; -use InvalidArgumentException; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Helpers\Utilities; @@ -25,49 +23,20 @@ class DatabaseManagementService */ private const MATCH_NAME_REGEX = '/^(s[\d]+_)(.*)$/'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository - */ - private $repository; - /** * Determines if the service should validate the user's ability to create an additional * database for this server. In almost all cases this should be true, but to keep things * flexible you can also set it to false and create more databases than the server is * allocated. - * - * @var bool */ - protected $validateDatabaseLimit = true; + protected bool $validateDatabaseLimit = true; - /** - * CreationService constructor. - */ public function __construct( - ConnectionInterface $connection, - DynamicDatabaseConnection $dynamic, - DatabaseRepository $repository, - Encrypter $encrypter + protected ConnectionInterface $connection, + protected DynamicDatabaseConnection $dynamic, + protected Encrypter $encrypter, + protected DatabaseRepository $repository, ) { - $this->connection = $connection; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** @@ -82,10 +51,8 @@ public static function generateUniqueDatabaseName(string $name, int $serverId): } /** - * Set wether or not this class should validate that the server has enough slots + * Set whether this class should validate that the server has enough slots * left before creating the new database. - * - * @return $this */ public function setValidateDatabaseLimit(bool $validate): self { @@ -97,13 +64,11 @@ public function setValidateDatabaseLimit(bool $validate): self /** * Create a new database that is linked to a specific host. * - * @return \Pterodactyl\Models\Database - * * @throws \Throwable - * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException - * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException + * @throws TooManyDatabasesException + * @throws DatabaseClientFeatureNotEnabledException */ - public function create(Server $server, array $data) + public function create(Server $server, array $data): Database { if (!config('pterodactyl.client_features.databases.enabled')) { throw new DatabaseClientFeatureNotEnabledException(); @@ -119,7 +84,7 @@ public function create(Server $server, array $data) // Protect against developer mistakes... if (empty($data['database']) || !preg_match(self::MATCH_NAME_REGEX, $data['database'])) { - throw new InvalidArgumentException('The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".'); + throw new \InvalidArgumentException('The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".'); } $data = array_merge($data, [ @@ -150,14 +115,18 @@ public function create(Server $server, array $data) return $database; }); - } catch (Exception $exception) { + } catch (\Exception $exception) { try { + // This is actually incorrect, it can be null in the case that the $database model + // itself isn't able to be created in Pterodactyl's database. + // + // @phpstan-ignore-next-line instanceof.alwaysFalse if ($database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); } - } catch (Exception $deletionException) { + } catch (\Throwable $deletionException) { // @phpstan-ignore catch.neverThrown // Do nothing here. We've already encountered an issue before this point so no // reason to prioritize this error over the initial one. } @@ -169,11 +138,9 @@ public function create(Server $server, array $data) /** * Delete a database from the given host server. * - * @return bool|null - * * @throws \Exception */ - public function delete(Database $database) + public function delete(Database $database): ?bool { $this->dynamic->set('dynamic', $database->database_host_id); @@ -189,7 +156,7 @@ public function delete(Database $database) * have the same name across multiple hosts, for the sake of keeping this logic easy to understand * and avoiding user confusion we will ignore the specific host and just look across all hosts. * - * @throws \Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException + * @throws DuplicateDatabaseNameException * @throws \Throwable */ protected function createModel(array $data): Database diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php index aabe983881..69510ed6ae 100644 --- a/app/Services/Databases/DatabasePasswordService.php +++ b/app/Services/Databases/DatabasePasswordService.php @@ -11,59 +11,35 @@ class DatabasePasswordService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $repository; - /** * DatabasePasswordService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseRepositoryInterface $repository, ) { - $this->connection = $connection; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Updates a password for a given database. * - * @param \Pterodactyl\Models\Database|int $database - * * @throws \Throwable */ - public function handle(Database $database): string + public function handle(Database|int $database): string { $password = Utilities::randomStringWithSpecialCharacters(24); $this->connection->transaction(function () use ($database, $password) { - $this->dynamic->set('dynamic', $database->database_host_id); + // Lock the row to serialize concurrent rotations of the same database. + $database->newQuery()->whereKey($database->getKey())->lockForUpdate()->firstOrFail(); - $this->repository->withoutFreshModel()->update($database->id, [ + $database->update([ 'password' => $this->encrypter->encrypt($password), ]); + $this->dynamic->set('dynamic', $database->database_host_id); $this->repository->dropUser($database->username, $database->remote); $this->repository->createUser($database->username, $database->remote, $password, $database->max_connections); $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index 3946cb83f8..e22eba51d4 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -11,18 +11,10 @@ class DeployServerDatabaseService { /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService + * DeployServerDatabaseService constructor. */ - private $managementService; - - /** - * ServerDatabaseCreationService constructor. - * - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService - */ - public function __construct(DatabaseManagementService $managementService) + public function __construct(private DatabaseManagementService $managementService) { - $this->managementService = $managementService; } /** diff --git a/app/Services/Databases/Hosts/HostCreationService.php b/app/Services/Databases/Hosts/HostCreationService.php index 275f4d294a..0eecd7971f 100644 --- a/app/Services/Databases/Hosts/HostCreationService.php +++ b/app/Services/Databases/Hosts/HostCreationService.php @@ -11,46 +11,16 @@ class HostCreationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - private $databaseManager; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - /** * HostCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseManager $databaseManager, - DatabaseHostRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DatabaseManager $databaseManager, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseHostRepositoryInterface $repository, ) { - $this->connection = $connection; - $this->databaseManager = $databaseManager; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** diff --git a/app/Services/Databases/Hosts/HostDeletionService.php b/app/Services/Databases/Hosts/HostDeletionService.php index 1538d4823f..8904121082 100644 --- a/app/Services/Databases/Hosts/HostDeletionService.php +++ b/app/Services/Databases/Hosts/HostDeletionService.php @@ -8,32 +8,20 @@ class HostDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $databaseRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - /** * HostDeletionService constructor. */ public function __construct( - DatabaseRepositoryInterface $databaseRepository, - DatabaseHostRepositoryInterface $repository + private DatabaseRepositoryInterface $databaseRepository, + private DatabaseHostRepositoryInterface $repository, ) { - $this->databaseRepository = $databaseRepository; - $this->repository = $repository; } /** * Delete a specified host from the Panel if no databases are * attached to it. * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws HasActiveServersException */ public function handle(int $host): int { diff --git a/app/Services/Databases/Hosts/HostUpdateService.php b/app/Services/Databases/Hosts/HostUpdateService.php index 4a281b851c..e2efd46fca 100644 --- a/app/Services/Databases/Hosts/HostUpdateService.php +++ b/app/Services/Databases/Hosts/HostUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Databases\Hosts; @@ -19,45 +12,15 @@ class HostUpdateService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - private $databaseManager; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - - /** - * DatabaseHostService constructor. + * HostUpdateService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseManager $databaseManager, - DatabaseHostRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DatabaseManager $databaseManager, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseHostRepositoryInterface $repository, ) { - $this->connection = $connection; - $this->databaseManager = $databaseManager; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** diff --git a/app/Services/Deployment/AllocationSelectionService.php b/app/Services/Deployment/AllocationSelectionService.php index d714700b59..7915fc5fe4 100644 --- a/app/Services/Deployment/AllocationSelectionService.php +++ b/app/Services/Deployment/AllocationSelectionService.php @@ -10,42 +10,25 @@ class AllocationSelectionService { - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $repository; - - /** - * @var bool - */ - protected $dedicated = false; + protected bool $dedicated = false; - /** - * @var array - */ - protected $nodes = []; + protected array $nodes = []; - /** - * @var array - */ - protected $ports = []; + protected array $ports = []; /** * AllocationSelectionService constructor. */ - public function __construct(AllocationRepositoryInterface $repository) + public function __construct(private AllocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Toggle if the selected allocation should be the only allocation belonging * to the given IP address. If true an allocation will not be selected if an IP * already has another server set to use on if its allocations. - * - * @return $this */ - public function setDedicated(bool $dedicated) + public function setDedicated(bool $dedicated): self { $this->dedicated = $dedicated; @@ -55,10 +38,8 @@ public function setDedicated(bool $dedicated) /** * A list of node IDs that should be used when selecting an allocation. If empty, all * nodes will be used to filter with. - * - * @return $this */ - public function setNodes(array $nodes) + public function setNodes(array $nodes): self { $this->nodes = $nodes; @@ -70,11 +51,9 @@ public function setNodes(array $nodes) * empty, all ports will be considered when finding an allocation. If set, only ports appearing * in the array or range will be used. * - * @return $this - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function setPorts(array $ports) + public function setPorts(array $ports): self { $stored = []; foreach ($ports as $port) { @@ -101,7 +80,7 @@ public function setPorts(array $ports) /** * Return a single allocation that should be used as the default allocation for a server. * - * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws NoViableAllocationException */ public function handle(): Allocation { diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 5c5a5138e6..cc76ad7182 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -4,29 +4,18 @@ use Pterodactyl\Models\Node; use Webmozart\Assert\Assert; +use Illuminate\Support\Collection; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException; class FindViableNodesService { - /** - * @var array - */ - protected $locations = []; - - /** - * @var int - */ - protected $disk; - - /** - * @var int - */ - protected $memory; + protected array $locations = []; + protected ?int $disk = null; + protected ?int $memory = null; /** * Set the locations that should be searched through to locate available nodes. - * - * @return $this */ public function setLocations(array $locations): self { @@ -41,8 +30,6 @@ public function setLocations(array $locations): self * Set the amount of disk that will be used by the server being created. Nodes will be * filtered out if they do not have enough available free disk space for this server * to be placed on. - * - * @return $this */ public function setDisk(int $disk): self { @@ -54,8 +41,6 @@ public function setDisk(int $disk): self /** * Set the amount of memory that this server will be using. As with disk space, nodes that * do not have enough free memory will be filtered out. - * - * @return $this */ public function setMemory(int $memory): self { @@ -79,11 +64,9 @@ public function setMemory(int $memory): self * If "null" is provided as the value no pagination will * be used. * - * @return \Illuminate\Support\Collection|\Illuminate\Contracts\Pagination\LengthAwarePaginator - * - * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + * @throws NoViableNodeException */ - public function handle(int $perPage = null, int $page = null) + public function handle(?int $perPage = null, ?int $page = null): LengthAwarePaginator|Collection { Assert::integer($this->disk, 'Disk space must be an int, got %s'); Assert::integer($this->memory, 'Memory usage must be an int, got %s'); diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php index 4dce8fd250..7eb10ed18d 100644 --- a/app/Services/Eggs/EggConfigurationService.php +++ b/app/Services/Eggs/EggConfigurationService.php @@ -9,17 +9,11 @@ class EggConfigurationService { - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - /** * EggConfigurationService constructor. */ - public function __construct(ServerConfigurationStructureService $configurationStructureService) + public function __construct(private ServerConfigurationStructureService $configurationStructureService) { - $this->configurationStructureService = $configurationStructureService; } /** @@ -41,10 +35,8 @@ public function handle(Server $server): array /** * Convert the "done" variable into an array if it is not currently one. - * - * @return array */ - protected function convertStartupToNewFormat(array $startup) + protected function convertStartupToNewFormat(array $startup): array { $done = Arr::get($startup, 'done'); @@ -72,12 +64,6 @@ protected function convertStopToNewFormat(string $stop): array } $signal = substr($stop, 1); - if (strtoupper($signal) === 'C') { - return [ - 'type' => 'stop', - 'value' => null, - ]; - } return [ 'type' => 'signal', @@ -85,10 +71,7 @@ protected function convertStopToNewFormat(string $stop): array ]; } - /** - * @return array - */ - protected function replacePlaceholders(Server $server, object $configs) + protected function replacePlaceholders(Server $server, object $configs): array { // Get the legacy configuration structure for the server so that we // can property map the egg placeholders to values. @@ -161,20 +144,15 @@ protected function replaceLegacyModifiers(string $key, string $value): string case 'env.SERVER_PORT': $replace = 'server.build.default.port'; break; - // By default we don't need to change anything, only if we ended up matching a specific legacy item. default: + // By default, we don't need to change anything, only if we ended up matching a specific legacy item. $replace = $key; } return str_replace("{{{$key}}}", "{{{$replace}}}", $value); } - /** - * @param mixed $value - * - * @return mixed|null - */ - protected function matchAndReplaceKeys($value, array $structure) + protected function matchAndReplaceKeys(mixed $value, array $structure): mixed { preg_match_all('/{{(?[\w.-]*)}}/', $value, $matches); @@ -229,12 +207,8 @@ protected function matchAndReplaceKeys($value, array $structure) * Iterates over a set of "find" values for a given file in the parser configuration. If * the value of the line match is something iterable, continue iterating, otherwise perform * a match & replace. - * - * @param mixed $data - * - * @return mixed */ - private function iterate($data, array $structure) + private function iterate(mixed $data, array $structure): mixed { if (!is_iterable($data) && !is_object($data)) { return $data; @@ -243,7 +217,13 @@ private function iterate($data, array $structure) // Remember, in PHP objects are always passed by reference, so if we do not clone this object // instance we'll end up making modifications to the object outside the scope of this function // which leads to some fun behavior in the parser. - $clone = clone $data; + if (is_array($data)) { + // Copy the array. + // NOTE: if the array contains any objects, they will be passed by reference. + $clone = $data; + } else { + $clone = clone $data; + } foreach ($clone as $key => &$value) { if (is_iterable($value) || is_object($value)) { $value = $this->iterate($value, $structure); diff --git a/app/Services/Eggs/EggCreationService.php b/app/Services/Eggs/EggCreationService.php index d7b5bb42bc..7379eae7c9 100644 --- a/app/Services/Eggs/EggCreationService.php +++ b/app/Services/Eggs/EggCreationService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -18,30 +11,18 @@ // When a mommy and a daddy pterodactyl really like each other... class EggCreationService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggCreationService constructor. */ - public function __construct(ConfigRepository $config, EggRepositoryInterface $repository) + public function __construct(private ConfigRepository $config, private EggRepositoryInterface $repository) { - $this->config = $config; - $this->repository = $repository; } /** * Create a new service option and assign it to the given service. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + * @throws NoParentConfigurationFoundException */ public function handle(array $data): Egg { diff --git a/app/Services/Eggs/EggDeletionService.php b/app/Services/Eggs/EggDeletionService.php index 1c8f8cf665..50b4e1c6d0 100644 --- a/app/Services/Eggs/EggDeletionService.php +++ b/app/Services/Eggs/EggDeletionService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -16,32 +9,20 @@ class EggDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - /** * EggDeletionService constructor. */ public function __construct( - ServerRepositoryInterface $serverRepository, - EggRepositoryInterface $repository + protected ServerRepositoryInterface $serverRepository, + protected EggRepositoryInterface $repository, ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; } /** * Delete an Egg from the database if it has no active servers attached to it. * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - * @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException + * @throws HasActiveServersException + * @throws HasChildrenException */ public function handle(int $egg): int { diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php new file mode 100644 index 0000000000..75add2352b --- /dev/null +++ b/app/Services/Eggs/EggParserService.php @@ -0,0 +1,90 @@ +getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); + } + + /** @var array $parsed */ + $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); + if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { + throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); + } + + return $this->convertToV2($parsed); + } + + /** + * Fills the provided model with the parsed JSON data. + */ + public function fillFromParsed(Egg $model, array $parsed): Egg + { + return $model->forceFill([ + 'name' => Arr::get($parsed, 'name'), + 'description' => Arr::get($parsed, 'description'), + 'features' => Arr::get($parsed, 'features'), + 'docker_images' => Arr::get($parsed, 'docker_images'), + 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist')) + ->filter(fn ($value) => !empty($value)), + 'update_url' => Arr::get($parsed, 'meta.update_url'), + 'config_files' => Arr::get($parsed, 'config.files'), + 'config_startup' => Arr::get($parsed, 'config.startup'), + 'config_logs' => Arr::get($parsed, 'config.logs'), + 'config_stop' => Arr::get($parsed, 'config.stop'), + 'startup' => Arr::get($parsed, 'startup'), + 'script_install' => Arr::get($parsed, 'scripts.installation.script'), + 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), + 'script_container' => Arr::get($parsed, 'scripts.installation.container'), + ]); + } + + /** + * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles + * the "docker_images" field potentially not being present, and not being in the + * expected "key => value" format. + */ + protected function convertToV2(array $parsed): array + { + if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) { + return $parsed; + } + + // Maintain backwards compatability for eggs that are still using the old single image + // string format. New eggs can provide an array of Docker images that can be used. + if (!isset($parsed['images'])) { + $images = [Arr::get($parsed, 'image') ?? 'nil']; + } else { + $images = $parsed['images']; + } + + unset($parsed['images'], $parsed['image']); + + $parsed['docker_images'] = []; + foreach ($images as $image) { + $parsed['docker_images'][$image] = $image; + } + + $parsed['variables'] = array_map(function ($value) { + return array_merge($value, ['field_type' => 'text']); + }, $parsed['variables']); + + return $parsed; + } +} diff --git a/app/Services/Eggs/EggUpdateService.php b/app/Services/Eggs/EggUpdateService.php index e0cabbb05e..64c2755bea 100644 --- a/app/Services/Eggs/EggUpdateService.php +++ b/app/Services/Eggs/EggUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -15,17 +8,11 @@ class EggUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggUpdateService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -33,9 +20,9 @@ public function __construct(EggRepositoryInterface $repository) * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + * @throws NoParentConfigurationFoundException */ - public function handle(Egg $egg, array $data) + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'config_from'))) { $results = $this->repository->findCountWhere([ diff --git a/app/Services/Eggs/Scripts/InstallScriptService.php b/app/Services/Eggs/Scripts/InstallScriptService.php index ecd1dc1f3a..a6f24f0b12 100644 --- a/app/Services/Eggs/Scripts/InstallScriptService.php +++ b/app/Services/Eggs/Scripts/InstallScriptService.php @@ -8,29 +8,21 @@ class InstallScriptService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * InstallScriptService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** * Modify the install script for a given Egg. * - * @param int|\Pterodactyl\Models\Egg $egg - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException + * @throws InvalidCopyFromException */ - public function handle(Egg $egg, array $data) + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'copy_script_from'))) { if (!$this->repository->isCopyableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) { diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index f646561509..706297b3de 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -3,23 +3,18 @@ namespace Pterodactyl\Services\Eggs\Sharing; use Carbon\Carbon; +use Pterodactyl\Models\Egg; use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; class EggExporterService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggExporterService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -34,15 +29,15 @@ public function handle(int $egg): string $struct = [ '_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO', 'meta' => [ - 'version' => 'PTDL_v1', + 'version' => Egg::EXPORT_VERSION, 'update_url' => $egg->update_url, ], - 'exported_at' => Carbon::now()->toIso8601String(), + 'exported_at' => Carbon::now()->toAtomString(), 'name' => $egg->name, 'author' => $egg->author, 'description' => $egg->description, 'features' => $egg->features, - 'images' => $egg->docker_images, + 'docker_images' => $egg->docker_images, 'file_denylist' => Collection::make($egg->inherit_file_denylist)->filter(function ($value) { return !empty($value); }), @@ -63,6 +58,7 @@ public function handle(int $egg): string 'variables' => $egg->variables->transform(function (EggVariable $item) { return Collection::make($item->toArray()) ->except(['id', 'egg_id', 'created_at', 'updated_at']) + ->merge(['field_type' => 'text']) ->toArray(); }), ]; diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 7292776904..997dc9236e 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -5,113 +5,46 @@ use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $eggVariableRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * EggImporterService constructor. - */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $eggVariableRepository, - NestRepositoryInterface $nestRepository - ) { - $this->connection = $connection; - $this->eggVariableRepository = $eggVariableRepository; - $this->repository = $repository; - $this->nestRepository = $nestRepository; + public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) + { } /** * Take an uploaded JSON file and parse it into a new egg. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ public function handle(UploadedFile $file, int $nest): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } + $parsed = $this->parser->handle($file); - /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true); - if (json_last_error() !== 0) { - throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); - } + /** @var Nest $nest */ + $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest); - if (Arr::get($parsed, 'meta.version') !== 'PTDL_v1') { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } + return $this->connection->transaction(function () use ($nest, $parsed) { + $egg = (new Egg())->forceFill([ + 'uuid' => Uuid::uuid4()->toString(), + 'nest_id' => $nest->id, + 'author' => Arr::get($parsed, 'author'), + 'copy_script_from' => null, + ]); - $nest = $this->nestRepository->getWithEggs($nest); - $this->connection->beginTransaction(); + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = $this->repository->create([ - 'uuid' => Uuid::uuid4()->toString(), - 'nest_id' => $nest->id, - 'author' => Arr::get($parsed, 'author'), - 'name' => Arr::get($parsed, 'name'), - 'description' => Arr::get($parsed, 'description'), - 'features' => Arr::get($parsed, 'features'), - // Maintain backwards compatability for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - 'docker_images' => Arr::get($parsed, 'images') ?? [Arr::get($parsed, 'image')], - 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))->filter(function ($value) { - return !empty($value); - }), - 'update_url' => Arr::get($parsed, 'meta.update_url'), - 'config_files' => Arr::get($parsed, 'config.files'), - 'config_startup' => Arr::get($parsed, 'config.startup'), - 'config_logs' => Arr::get($parsed, 'config.logs'), - 'config_stop' => Arr::get($parsed, 'config.stop'), - 'startup' => Arr::get($parsed, 'startup'), - 'script_install' => Arr::get($parsed, 'scripts.installation.script'), - 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => Arr::get($parsed, 'scripts.installation.container'), - 'copy_script_from' => null, - ], true, true); + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::query()->forceCreate(array_merge($variable, ['egg_id' => $egg->id])); + } - Collection::make($parsed['variables'] ?? [])->each(function (array $variable) use ($egg) { - $this->eggVariableRepository->create(array_merge($variable, [ - 'egg_id' => $egg->id, - ])); + return $egg; }); - - $this->connection->commit(); - - return $egg; } } diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 205314314e..89a1f92873 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -4,105 +4,47 @@ use Pterodactyl\Models\Egg; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggUpdateImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $variableRepository; - /** * EggUpdateImporterService constructor. */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $variableRepository - ) { - $this->connection = $connection; - $this->repository = $repository; - $this->variableRepository = $variableRepository; + public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) + { } /** * Update an existing Egg using an uploaded JSON file. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ - public function handle(Egg $egg, UploadedFile $file) + public function handle(Egg $egg, UploadedFile $file): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } - - $parsed = json_decode($file->openFile()->fread($file->getSize())); - if (json_last_error() !== 0) { - throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); - } - - if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } - - $this->connection->beginTransaction(); - $this->repository->update($egg->id, [ - 'author' => object_get($parsed, 'author'), - 'name' => object_get($parsed, 'name'), - 'description' => object_get($parsed, 'description'), - 'features' => object_get($parsed, 'features'), - // Maintain backwards compatibility for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - 'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')], - 'config_files' => object_get($parsed, 'config.files'), - 'config_startup' => object_get($parsed, 'config.startup'), - 'config_logs' => object_get($parsed, 'config.logs'), - 'config_stop' => object_get($parsed, 'config.stop'), - 'startup' => object_get($parsed, 'startup'), - 'script_install' => object_get($parsed, 'scripts.installation.script'), - 'script_entry' => object_get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => object_get($parsed, 'scripts.installation.container'), - ], true, true); + $parsed = $this->parser->handle($file); + + return $this->connection->transaction(function () use ($egg, $parsed) { + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); + + // Update existing variables or create new ones. + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::unguarded(function () use ($egg, $variable) { + $egg->variables()->updateOrCreate([ + 'env_variable' => $variable['env_variable'], + ], Collection::make($variable)->except('egg_id', 'env_variable')->toArray()); + }); + } - // Update Existing Variables - collect($parsed->variables)->each(function ($variable) use ($egg) { - $this->variableRepository->withoutFreshModel()->updateOrCreate([ - 'egg_id' => $egg->id, - 'env_variable' => $variable->env_variable, - ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); - }); + $imported = array_map(fn ($value) => $value['env_variable'], $parsed['variables'] ?? []); - $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); - $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg->id]]); + $egg->variables()->whereNotIn('env_variable', $imported)->delete(); - // Delete variables not present in the import. - collect($existing)->each(function ($variable) use ($egg, $imported) { - if (!in_array($variable->env_variable, $imported)) { - $this->variableRepository->deleteWhere([ - ['egg_id', '=', $egg->id], - ['env_variable', '=', $variable->env_variable], - ]); - } + return $egg->refresh(); }); - - $this->connection->commit(); } } diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php index fa758265b0..601a3513a8 100644 --- a/app/Services/Eggs/Variables/VariableCreationService.php +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Services\Eggs\Variables; use Pterodactyl\Models\EggVariable; -use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Traits\Services\ValidatesValidationRules; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; @@ -12,30 +12,18 @@ class VariableCreationService { use ValidatesValidationRules; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableCreationService constructor. */ - public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) + public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) { - $this->repository = $repository; - $this->validator = $validator; } /** * Return the validation factory instance to be used by rule validation * checking in the trait. */ - protected function getValidator(): Factory + protected function getValidator(): ValidationFactory { return $this->validator; } @@ -45,7 +33,7 @@ protected function getValidator(): Factory * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException - * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + * @throws ReservedVariableNameException */ public function handle(int $egg, array $data): EggVariable { diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index da4426c339..c3ca303bde 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -4,9 +4,9 @@ use Illuminate\Support\Str; use Pterodactyl\Models\EggVariable; -use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; @@ -14,30 +14,18 @@ class VariableUpdateService { use ValidatesValidationRules; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableUpdateService constructor. */ - public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) + public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) { - $this->repository = $repository; - $this->validator = $validator; } /** * Return the validation factory instance to be used by rule validation * checking in the trait. */ - protected function getValidator(): Factory + protected function getValidator(): ValidationFactory { return $this->validator; } @@ -45,14 +33,12 @@ protected function getValidator(): Factory /** * Update a specific egg variable. * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + * @throws ReservedVariableNameException */ - public function handle(EggVariable $variable, array $data) + public function handle(EggVariable $variable, array $data): mixed { if (!is_null(array_get($data, 'env_variable'))) { if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { diff --git a/app/Services/Helpers/ApiAllowedIpsValidatorService.php b/app/Services/Helpers/ApiAllowedIpsValidatorService.php deleted file mode 100644 index 7051cd5399..0000000000 --- a/app/Services/Helpers/ApiAllowedIpsValidatorService.php +++ /dev/null @@ -1,8 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index 4b1bf7e0b8..725a566692 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -4,7 +4,8 @@ use Illuminate\Support\Arr; use Illuminate\Filesystem\FilesystemManager; -use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\Filesystem\Filesystem; +use Pterodactyl\Exceptions\ManifestDoesNotExistException; class AssetHashService { @@ -13,34 +14,20 @@ class AssetHashService */ public const MANIFEST_PATH = './assets/manifest.json'; - /** - * @var \Illuminate\Contracts\Filesystem\Filesystem - */ - private $filesystem; + private Filesystem $filesystem; - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - private $application; - - /** - * @var array|null - */ - protected static $manifest; + protected static mixed $manifest = null; /** * AssetHashService constructor. */ - public function __construct(Application $application, FilesystemManager $filesystem) + public function __construct(FilesystemManager $filesystem) { - $this->application = $application; $this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]); } /** * Modify a URL to append the asset hash. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function url(string $resource): string { @@ -52,8 +39,6 @@ public function url(string $resource): string /** * Return the data integrity hash for a resource. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function integrity(string $resource): string { @@ -65,8 +50,6 @@ public function integrity(string $resource): string /** * Return a built CSS import using the provided URL. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function css(string $resource): string { @@ -92,8 +75,6 @@ public function css(string $resource): string /** * Return a built JS import using the provided URL. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function js(string $resource): string { @@ -116,14 +97,21 @@ public function js(string $resource): string /** * Get the asset manifest and store it in the cache for quicker lookups. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ protected function manifest(): array { - return self::$manifest ?: self::$manifest = json_decode( - $this->filesystem->get(self::MANIFEST_PATH), - true - ); + if (static::$manifest === null) { + self::$manifest = json_decode( + $this->filesystem->get(self::MANIFEST_PATH), + true + ); + } + + $manifest = static::$manifest; + if ($manifest === null) { + throw new ManifestDoesNotExistException(); + } + + return $manifest; } } diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 14a2b437d5..a2d30255bc 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Services\Helpers; -use Exception; use GuzzleHttp\Client; use Carbon\CarbonImmutable; use Illuminate\Support\Arr; @@ -13,98 +12,68 @@ class SoftwareVersionService { public const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; - /** - * @var array - */ - private static $result; - - /** - * @var \Illuminate\Contracts\Cache\Repository - */ - protected $cache; - - /** - * @var \GuzzleHttp\Client - */ - protected $client; + private static array $result; /** * SoftwareVersionService constructor. */ public function __construct( - CacheRepository $cache, - Client $client + protected CacheRepository $cache, + protected Client $client, ) { - $this->cache = $cache; - $this->client = $client; - self::$result = $this->cacheVersionData(); } /** * Get the latest version of the panel from the CDN servers. - * - * @return string */ - public function getPanel() + public function getPanel(): string { return Arr::get(self::$result, 'panel') ?? 'error'; } /** * Get the latest version of the daemon from the CDN servers. - * - * @return string */ - public function getDaemon() + public function getDaemon(): string { return Arr::get(self::$result, 'wings') ?? 'error'; } /** * Get the URL to the discord server. - * - * @return string */ - public function getDiscord() + public function getDiscord(): string { return Arr::get(self::$result, 'discord') ?? 'https://pterodactyl.io/discord'; } /** * Get the URL for donations. - * - * @return string */ - public function getDonations() + public function getDonations(): string { - return Arr::get(self::$result, 'donations') ?? 'https://paypal.me/PterodactylSoftware'; + return Arr::get(self::$result, 'donations') ?? 'https://github.com/sponsors/matthewpi'; } /** * Determine if the current version of the panel is the latest. - * - * @return bool */ - public function isLatestPanel() + public function isLatestPanel(): bool { - if (config()->get('app.version') === 'canary') { + if (config('app.version') === 'canary') { return true; } - return version_compare(config()->get('app.version'), $this->getPanel()) >= 0; + return version_compare(config('app.version'), $this->getPanel()) >= 0; } /** * Determine if a passed daemon version string is the latest. - * - * @param string $version - * - * @return bool */ - public function isLatestDaemon($version) + public function isLatestDaemon(string $version): bool { - if ($version === '0.0.0-canary') { + if ($version === 'develop') { return true; } @@ -113,21 +82,19 @@ public function isLatestDaemon($version) /** * Keeps the versioning cache up-to-date with the latest results from the CDN. - * - * @return array */ - protected function cacheVersionData() + protected function cacheVersionData(): array { - return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { + return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', config()->get('pterodactyl.cdn.url')); + $response = $this->client->request('GET', config('pterodactyl.cdn.url')); if ($response->getStatusCode() === 200) { return json_decode($response->getBody(), true); } throw new CdnVersionFetchingException(); - } catch (Exception $exception) { + } catch (\Exception) { return []; } }); diff --git a/app/Services/Locations/LocationCreationService.php b/app/Services/Locations/LocationCreationService.php index cff493a8bf..b1a3ec9956 100644 --- a/app/Services/Locations/LocationCreationService.php +++ b/app/Services/Locations/LocationCreationService.php @@ -1,39 +1,25 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; +use Pterodactyl\Models\Location; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationCreationService { - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationCreationService constructor. */ - public function __construct(LocationRepositoryInterface $repository) + public function __construct(protected LocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Create a new location. * - * @return \Pterodactyl\Models\Location - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): Location { return $this->repository->create($data); } diff --git a/app/Services/Locations/LocationDeletionService.php b/app/Services/Locations/LocationDeletionService.php index 2ffd38187c..0e335946c9 100644 --- a/app/Services/Locations/LocationDeletionService.php +++ b/app/Services/Locations/LocationDeletionService.php @@ -1,15 +1,7 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; -use Webmozart\Assert\Assert; use Pterodactyl\Models\Location; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; @@ -17,41 +9,23 @@ class LocationDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $nodeRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationDeletionService constructor. */ public function __construct( - LocationRepositoryInterface $repository, - NodeRepositoryInterface $nodeRepository + protected LocationRepositoryInterface $repository, + protected NodeRepositoryInterface $nodeRepository, ) { - $this->nodeRepository = $nodeRepository; - $this->repository = $repository; } /** * Delete an existing location. * - * @param int|\Pterodactyl\Models\Location $location - * - * @return int|null - * - * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException + * @throws HasActiveNodesException */ - public function handle($location) + public function handle(Location|int $location): ?int { - $location = ($location instanceof Location) ? $location->id : $location; - - Assert::integerish($location, 'First argument passed to handle must be numeric or an instance of ' . Location::class . ', received %s.'); + $location = $location instanceof Location ? $location->id : $location; $count = $this->nodeRepository->findCountWhere([['location_id', '=', $location]]); if ($count > 0) { diff --git a/app/Services/Locations/LocationUpdateService.php b/app/Services/Locations/LocationUpdateService.php index 87399201f6..cf24459e92 100644 --- a/app/Services/Locations/LocationUpdateService.php +++ b/app/Services/Locations/LocationUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; @@ -14,30 +7,20 @@ class LocationUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationUpdateService constructor. */ - public function __construct(LocationRepositoryInterface $repository) + public function __construct(protected LocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Update an existing location. * - * @param int|\Pterodactyl\Models\Location $location - * - * @return \Pterodactyl\Models\Location - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($location, array $data) + public function handle(Location|int $location, array $data): Location { $location = ($location instanceof Location) ? $location->id : $location; diff --git a/app/Services/Nests/NestCreationService.php b/app/Services/Nests/NestCreationService.php index af56496d19..7fbb1eb722 100644 --- a/app/Services/Nests/NestCreationService.php +++ b/app/Services/Nests/NestCreationService.php @@ -9,23 +9,11 @@ class NestCreationService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; - /** * NestCreationService constructor. */ - public function __construct(ConfigRepository $config, NestRepositoryInterface $repository) + public function __construct(private ConfigRepository $config, private NestRepositoryInterface $repository) { - $this->config = $config; - $this->repository = $repository; } /** @@ -33,7 +21,7 @@ public function __construct(ConfigRepository $config, NestRepositoryInterface $r * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data, string $author = null): Nest + public function handle(array $data, ?string $author = null): Nest { return $this->repository->create([ 'uuid' => Uuid::uuid4()->toString(), diff --git a/app/Services/Nests/NestDeletionService.php b/app/Services/Nests/NestDeletionService.php index 777a41b683..bb5271133d 100644 --- a/app/Services/Nests/NestDeletionService.php +++ b/app/Services/Nests/NestDeletionService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Nests; @@ -15,31 +8,19 @@ class NestDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - /** * NestDeletionService constructor. */ public function __construct( - ServerRepositoryInterface $serverRepository, - NestRepositoryInterface $repository + protected ServerRepositoryInterface $serverRepository, + protected NestRepositoryInterface $repository, ) { - $this->serverRepository = $serverRepository; - $this->repository = $repository; } /** * Delete a nest from the system only if there are no servers attached to it. * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws HasActiveServersException */ public function handle(int $nest): int { diff --git a/app/Services/Nests/NestUpdateService.php b/app/Services/Nests/NestUpdateService.php index 7772ff380d..772421e42e 100644 --- a/app/Services/Nests/NestUpdateService.php +++ b/app/Services/Nests/NestUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Nests; @@ -13,17 +6,11 @@ class NestUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - /** * NestUpdateService constructor. */ - public function __construct(NestRepositoryInterface $repository) + public function __construct(protected NestRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -32,7 +19,7 @@ public function __construct(NestRepositoryInterface $repository) * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(int $nest, array $data) + public function handle(int $nest, array $data): void { if (!is_null(array_get($data, 'author'))) { unset($data['author']); diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php index e8ea5b0d8b..b21589c4a3 100644 --- a/app/Services/Nodes/NodeCreationService.php +++ b/app/Services/Nodes/NodeCreationService.php @@ -11,35 +11,21 @@ class NodeCreationService { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * NodeCreationService constructor. */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * CreationService constructor. - */ - public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository) + public function __construct(protected NodeRepositoryInterface $repository) { - $this->repository = $repository; - $this->encrypter = $encrypter; } /** * Create a new node on the panel. * - * @return \Pterodactyl\Models\Node - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): Node { $data['uuid'] = Uuid::uuid4()->toString(); - $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); + $data['daemon_token'] = app(Encrypter::class)->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); return $this->repository->create($data, true, true); diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php index e77104e09a..f7966e009e 100644 --- a/app/Services/Nodes/NodeDeletionService.php +++ b/app/Services/Nodes/NodeDeletionService.php @@ -1,13 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - namespace Pterodactyl\Services\Nodes; use Pterodactyl\Models\Node; @@ -19,43 +11,21 @@ class NodeDeletionService { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * DeletionService constructor. + * NodeDeletionService constructor. */ public function __construct( - NodeRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository, - Translator $translator + protected NodeRepositoryInterface $repository, + protected ServerRepositoryInterface $serverRepository, + protected Translator $translator, ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->translator = $translator; } /** * Delete a node from the panel if no servers are attached to it. * - * @param int|\Pterodactyl\Models\Node $node - * - * @return bool|null - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws HasActiveServersException */ - public function handle($node) + public function handle(int|Node $node): int { if ($node instanceof Node) { $node = $node->id; diff --git a/app/Services/Nodes/NodeJWTService.php b/app/Services/Nodes/NodeJWTService.php index 1b52479ba6..e757f49ad2 100644 --- a/app/Services/Nodes/NodeJWTService.php +++ b/app/Services/Nodes/NodeJWTService.php @@ -2,58 +2,66 @@ namespace Pterodactyl\Services\Nodes; -use DateTimeImmutable; use Carbon\CarbonImmutable; use Illuminate\Support\Str; use Pterodactyl\Models\Node; +use Pterodactyl\Models\User; +use Webmozart\Assert\Assert; +use Pterodactyl\Enum\JwtScope; use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\UnencryptedToken; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Key\InMemory; use Pterodactyl\Extensions\Lcobucci\JWT\Encoding\TimestampDates; class NodeJWTService { - /** - * @var array - */ - private $claims = []; + private array $claims = []; - /** - * @var \DateTimeImmutable|null - */ - private $expiresAt; + private array $scopes; - /** - * @var string|null - */ - private $subject; + private ?User $user = null; + + private \DateTimeImmutable $expiresAt; + + private ?string $subject = null; /** * Set the claims to include in this JWT. - * - * @return $this */ - public function setClaims(array $claims) + public function setClaims(array $claims): self { $this->claims = $claims; return $this; } + public function setScopes(JwtScope ...$scopes): self + { + $this->scopes = $scopes; + + return $this; + } + /** - * @return $this + * Attaches a user to the JWT being created and will automatically inject the + * "user_uuid" key into the final claims array with the user's UUID. */ - public function setExpiresAt(DateTimeImmutable $date) + public function setUser(User $user): self + { + $this->user = $user; + + return $this; + } + + public function setExpiresAt(\DateTimeImmutable $date): self { $this->expiresAt = $date; return $this; } - /** - * @return $this - */ - public function setSubject(string $subject) + public function setSubject(string $subject): self { $this->subject = $subject; @@ -62,14 +70,10 @@ public function setSubject(string $subject) /** * Generate a new JWT for a given node. - * - * @param string|null $identifiedBy - * - * @return \Lcobucci\JWT\Token\Plain */ - public function handle(Node $node, string $identifiedBy, string $algo = 'md5') + public function handle(Node $node, ?string $identifiedBy): UnencryptedToken { - $identifier = hash($algo, $identifiedBy); + $identifier = hash('sha256', $identifiedBy); $config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->getDecryptedKey())); $builder = $config->builder(new TimestampDates()) @@ -80,7 +84,7 @@ public function handle(Node $node, string $identifiedBy, string $algo = 'md5') ->issuedAt(CarbonImmutable::now()) ->canOnlyBeUsedAfter(CarbonImmutable::now()->subMinutes(5)); - if ($this->expiresAt) { + if (isset($this->expiresAt)) { $builder = $builder->expiresAt($this->expiresAt); } @@ -92,8 +96,16 @@ public function handle(Node $node, string $identifiedBy, string $algo = 'md5') $builder = $builder->withClaim($key, $value); } + Assert::notEmpty($this->scopes, 'Cannot generate a JWT without providing at least one scope.'); + + $builder = $builder->withClaim('scope', implode(' ', array_map(fn ($scope) => $scope->value, $this->scopes))); + + if (!is_null($this->user)) { + $builder = $builder->withClaim('user_uuid', $this->user->uuid); + } + return $builder - ->withClaim('unique_id', Str::random(16)) + ->withClaim('unique_id', Str::random()) ->getToken($config->signer(), $config->signingKey()); } } diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 9ba56fb4e8..11078af531 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -15,48 +15,22 @@ class NodeUpdateService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $configurationRepository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * UpdateService constructor. + * NodeUpdateService constructor. */ public function __construct( - ConnectionInterface $connection, - Encrypter $encrypter, - DaemonConfigurationRepository $configurationRepository, - NodeRepository $repository + private ConnectionInterface $connection, + private DaemonConfigurationRepository $configurationRepository, + private Encrypter $encrypter, + private NodeRepository $repository, ) { - $this->connection = $connection; - $this->configurationRepository = $configurationRepository; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Update the configuration values for a given node on the machine. * - * @return \Pterodactyl\Models\Node - * * @throws \Throwable */ - public function handle(Node $node, array $data, bool $resetToken = false) + public function handle(Node $node, array $data, bool $resetToken = false): Node { if ($resetToken) { $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); @@ -64,7 +38,7 @@ public function handle(Node $node, array $data, bool $resetToken = false) } [$updated, $exception] = $this->connection->transaction(function () use ($data, $node) { - /** @var \Pterodactyl\Models\Node $updated */ + /** @var Node $updated */ $updated = $this->repository->withFreshModel()->update($node->id, $data, true, true); try { @@ -87,7 +61,7 @@ public function handle(Node $node, array $data, bool $resetToken = false) // but something went wrong with Wings we just want to store the update and let the user manually // make changes as needed. // - // This avoids issues with proxies such as CloudFlare which will see Wings as offline and then + // This avoids issues with proxies such as Cloudflare which will see Wings as offline and then // inject their own response pages, causing this logic to get fucked up. // // @see https://github.com/pterodactyl/panel/issues/2712 diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index a131ad5739..ddaa10bc77 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -13,29 +13,11 @@ class ProcessScheduleService { - /** - * @var \Illuminate\Contracts\Bus\Dispatcher - */ - private $dispatcher; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * ProcessScheduleService constructor. */ - public function __construct(ConnectionInterface $connection, DaemonServerRepository $serverRepository, Dispatcher $dispatcher) + public function __construct(private ConnectionInterface $connection, private Dispatcher $dispatcher, private DaemonServerRepository $serverRepository) { - $this->dispatcher = $dispatcher; - $this->connection = $connection; - $this->serverRepository = $serverRepository; } /** @@ -43,11 +25,9 @@ public function __construct(ConnectionInterface $connection, DaemonServerReposit * * @throws \Throwable */ - public function handle(Schedule $schedule, bool $now = false) + public function handle(Schedule $schedule, bool $now = false): void { - /** @var \Pterodactyl\Models\Task $task */ $task = $schedule->tasks()->orderBy('sequence_id')->first(); - if (is_null($task)) { throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.'); } @@ -74,7 +54,7 @@ public function handle(Schedule $schedule, bool $now = false) return; } - } catch (Exception $exception) { + } catch (\Exception $exception) { if (!$exception instanceof DaemonConnectionException) { // If we encountered some exception during this process that wasn't just an // issue connecting to Wings run the failed sequence for a job. Otherwise we @@ -96,7 +76,7 @@ public function handle(Schedule $schedule, bool $now = false) // @see https://github.com/pterodactyl/panel/issues/2550 try { $this->dispatcher->dispatchNow($job); - } catch (Exception $exception) { + } catch (\Exception $exception) { $job->failed($exception); throw $exception; diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 66eb52235a..620deddd9b 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -14,56 +14,32 @@ class BuildModificationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $structureService; - /** * BuildModificationService constructor. - * - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository */ public function __construct( - ServerConfigurationStructureService $structureService, - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private ServerConfigurationStructureService $structureService, ) { - $this->daemonServerRepository = $daemonServerRepository; - $this->connection = $connection; - $this->structureService = $structureService; } /** * Change the build details for a specified server. * - * @return \Pterodactyl\Models\Server - * * @throws \Throwable - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function handle(Server $server, array $data) + public function handle(Server $server, array $data): Server { - /** @var \Pterodactyl\Models\Server $server */ - $server = $this->connection->transaction(function() use ($server, $data) { + /** @var Server $server */ + $server = $this->connection->transaction(function () use ($server, $data) { $this->processAllocations($server, $data); if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { try { Allocation::query()->where('id', $data['allocation_id'])->where('server_id', $server->id)->firstOrFail(); - } catch (ModelNotFoundException $ex) { + } catch (ModelNotFoundException) { throw new DisplayException('The requested default allocation is not currently assigned to this server.'); } } @@ -101,9 +77,9 @@ public function handle(Server $server, array $data) /** * Process the allocations being assigned in the data and ensure they are available for a server. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - private function processAllocations(Server $server, array &$data) + private function processAllocations(Server $server, array &$data): void { if (empty($data['add_allocations']) && empty($data['remove_allocations'])) { return; @@ -119,7 +95,7 @@ private function processAllocations(Server $server, array &$data) // Keep track of all the allocations we're just now adding so that we can use the first // one to reset the default allocation to. - $freshlyAllocated = $query->pluck('id')->first(); + $freshlyAllocated = $query->pluck('id')->first(); // @phpstan-ignore larastan.noUnnecessaryCollectionCall $query->update(['server_id' => $server->id, 'notes' => null]); } diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php index cbe2a6ed9e..e4a1b6f91a 100644 --- a/app/Services/Servers/DetailsModificationService.php +++ b/app/Services/Servers/DetailsModificationService.php @@ -4,32 +4,24 @@ use Illuminate\Support\Arr; use Pterodactyl\Models\Server; +use Pterodactyl\Jobs\RevokeSftpAccessJob; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Traits\Services\ReturnsUpdatedModels; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Repositories\Wings\DaemonRevocationRepository; class DetailsModificationService { use ReturnsUpdatedModels; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * DetailsModificationService constructor. */ - public function __construct(ConnectionInterface $connection, DaemonServerRepository $serverRepository) - { - $this->connection = $connection; - $this->serverRepository = $serverRepository; + public function __construct( + private ConnectionInterface $connection, + private DaemonServerRepository $serverRepository, + private DaemonRevocationRepository $revocationRepository, + ) { } /** @@ -40,7 +32,7 @@ public function __construct(ConnectionInterface $connection, DaemonServerReposit public function handle(Server $server, array $data): Server { return $this->connection->transaction(function () use ($data, $server) { - $owner = $server->owner_id; + $original = $server->user; $server->forceFill([ 'external_id' => Arr::get($data, 'external_id'), @@ -52,14 +44,8 @@ public function handle(Server $server, array $data): Server // If the owner_id value is changed we need to revoke any tokens that exist for the server // on the Wings instance so that the old owner no longer has any permission to access the // websockets. - if ($server->owner_id !== $owner) { - try { - $this->serverRepository->setServer($server)->revokeUserJTI($owner); - } catch (DaemonConnectionException $exception) { - // Do nothing. A failure here is not ideal, but it is likely to be caused by Wings - // being offline, or in an entirely broken state. Remeber, these tokens reset every - // few minutes by default, we're just trying to help it along a little quicker. - } + if (! $server->refresh()->user->is($original)) { + RevokeSftpAccessJob::dispatch($original->uuid, $server); } return $server; diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php index 54b82bab32..8f45bab2da 100644 --- a/app/Services/Servers/EnvironmentService.php +++ b/app/Services/Servers/EnvironmentService.php @@ -7,16 +7,13 @@ class EnvironmentService { - /** - * @var array - */ - private $additional = []; + private array $additional = []; /** * Dynamically configure additional environment variables to be assigned * with a specific server. */ - public function setEnvironmentKey(string $key, callable $closure) + public function setEnvironmentKey(string $key, callable $closure): void { $this->additional[$key] = $closure; } diff --git a/app/Services/Servers/GetUserPermissionsService.php b/app/Services/Servers/GetUserPermissionsService.php index fdac03bfdf..ae91cb33ff 100644 --- a/app/Services/Servers/GetUserPermissionsService.php +++ b/app/Services/Servers/GetUserPermissionsService.php @@ -11,10 +11,8 @@ class GetUserPermissionsService * Returns the server specific permissions that a user has. This checks * if they are an admin or a subuser for the server. If no permissions are * found, an empty array is returned. - * - * @return string[] */ - public function handle(Server $server, User $user) + public function handle(Server $server, User $user): array { if ($user->root_admin || $user->id === $server->owner_id) { $permissions = ['*']; diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index 561512b379..1822238288 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -8,35 +8,21 @@ class ReinstallServerService { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - /** * ReinstallService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, ) { - $this->daemonServerRepository = $daemonServerRepository; - $this->connection = $connection; } /** * Reinstall a server on the remote daemon. * - * @return \Pterodactyl\Models\Server - * * @throws \Throwable */ - public function handle(Server $server) + public function handle(Server $server): Server { return $this->connection->transaction(function () use ($server) { $server->fill(['status' => Server::STATUS_INSTALLING])->save(); diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 8d68d7de80..72f3277967 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -7,14 +7,11 @@ class ServerConfigurationStructureService { - private EnvironmentService $environment; - /** * ServerConfigurationStructureService constructor. */ - public function __construct(EnvironmentService $environment) + public function __construct(private EnvironmentService $environment) { - $this->environment = $environment; } /** @@ -47,6 +44,10 @@ protected function returnCurrentFormat(Server $server): array { return [ 'uuid' => $server->uuid, + 'meta' => [ + 'name' => $server->name, + 'description' => $server->description, + ], 'suspended' => $server->isSuspended(), 'environment' => $this->environment->handle($server), 'invocation' => $server->startup, @@ -69,6 +70,7 @@ protected function returnCurrentFormat(Server $server): array 'requires_rebuild' => false, ], 'allocations' => [ + 'force_outgoing_ip' => $server->egg->force_outgoing_ip, 'default' => [ 'ip' => $server->allocation->ip, 'port' => $server->allocation->port, diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 48fa8cc334..6f900b12d5 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -12,7 +12,6 @@ use Pterodactyl\Models\Allocation; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Models\Objects\DeploymentObject; -use Pterodactyl\Repositories\Eloquent\EggRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Deployment\FindViableNodesService; @@ -23,84 +22,18 @@ class ServerCreationService { /** - * @var \Pterodactyl\Services\Deployment\AllocationSelectionService - */ - private $allocationSelectionService; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private $findViableNodesService; - - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $validatorService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\EggRepository - */ - private $eggRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository - */ - private $serverVariableRepository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - private $serverDeletionService; - - /** - * CreationService constructor. - * - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Pterodactyl\Services\Servers\ServerDeletionService $serverDeletionService - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * ServerCreationService constructor. */ public function __construct( - AllocationSelectionService $allocationSelectionService, - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, - EggRepository $eggRepository, - FindViableNodesService $findViableNodesService, - ServerConfigurationStructureService $configurationStructureService, - ServerDeletionService $serverDeletionService, - ServerRepository $repository, - ServerVariableRepository $serverVariableRepository, - VariableValidatorService $validatorService + private AllocationSelectionService $allocationSelectionService, + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private FindViableNodesService $findViableNodesService, + private ServerRepository $repository, + private ServerDeletionService $serverDeletionService, + private ServerVariableRepository $serverVariableRepository, + private VariableValidatorService $validatorService, ) { - $this->allocationSelectionService = $allocationSelectionService; - $this->configurationStructureService = $configurationStructureService; - $this->connection = $connection; - $this->findViableNodesService = $findViableNodesService; - $this->validatorService = $validatorService; - $this->eggRepository = $eggRepository; - $this->repository = $repository; - $this->serverVariableRepository = $serverVariableRepository; - $this->daemonServerRepository = $daemonServerRepository; - $this->serverDeletionService = $serverDeletionService; } /** @@ -116,7 +49,7 @@ public function __construct( * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException */ - public function handle(array $data, DeploymentObject $deployment = null): Server + public function handle(array $data, ?DeploymentObject $deployment = null): Server { // If a deployment object has been passed we need to get the allocation // that the server should use, and assign the node from that allocation. @@ -149,7 +82,7 @@ public function handle(array $data, DeploymentObject $deployment = null): Server // // If that connection fails out we will attempt to perform a cleanup by just // deleting the server itself from the system. - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ $server = $this->connection->transaction(function () use ($data, $eggVariableData) { // Create the server and assign any additional allocations to it. $server = $this->createModel($data); @@ -182,7 +115,7 @@ public function handle(array $data, DeploymentObject $deployment = null): Server */ private function configureDeployment(array $data, DeploymentObject $deployment): Allocation { - /** @var \Illuminate\Support\Collection $nodes */ + /** @var Collection $nodes */ $nodes = $this->findViableNodesService->setLocations($deployment->getLocations()) ->setDisk(Arr::get($data, 'disk')) ->setMemory(Arr::get($data, 'memory')) @@ -203,7 +136,7 @@ private function createModel(array $data): Server { $uuid = $this->generateUniqueUuidCombo(); - /** @var \Pterodactyl\Models\Server $model */ + /** @var Server $model */ $model = $this->repository->create([ 'external_id' => Arr::get($data, 'external_id'), 'uuid' => $uuid, @@ -237,7 +170,7 @@ private function createModel(array $data): Server /** * Configure the allocations assigned to this server. */ - private function storeAssignedAllocations(Server $server, array $data) + private function storeAssignedAllocations(Server $server, array $data): void { $records = [$data['allocation_id']]; if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { @@ -252,7 +185,7 @@ private function storeAssignedAllocations(Server $server, array $data) /** * Process environment variables passed for this server and store them in the database. */ - private function storeEggVariables(Server $server, Collection $variables) + private function storeEggVariables(Server $server, Collection $variables): void { $records = $variables->map(function ($result) use ($server) { return [ diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index cc973ff845..b06329ea47 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Services\Servers; -use Exception; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Support\Facades\Log; @@ -13,47 +12,22 @@ class ServerDeletionService { - /** - * @var bool - */ - protected $force = false; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; + protected bool $force = false; /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $databaseManagementService; - - /** - * DeletionService constructor. + * ServerDeletionService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, - DatabaseManagementService $databaseManagementService + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private DatabaseManagementService $databaseManagementService, ) { - $this->connection = $connection; - $this->daemonServerRepository = $daemonServerRepository; - $this->databaseManagementService = $databaseManagementService; } /** * Set if the server should be forcibly deleted from the panel (ignoring daemon errors) or not. - * - * @param bool $bool - * - * @return $this */ - public function withForce($bool = true) + public function withForce(bool $bool = true): self { $this->force = $bool; @@ -61,12 +35,12 @@ public function withForce($bool = true) } /** - * Delete a server from the panel and remove any associated databases from hosts. + * Delete a server from the panel, clear any allocation notes, and remove any associated databases from hosts. * * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle(Server $server) + public function handle(Server $server): void { try { $this->daemonServerRepository->setServer($server)->delete(); @@ -86,7 +60,7 @@ public function handle(Server $server) foreach ($server->databases as $database) { try { $this->databaseManagementService->delete($database); - } catch (Exception $exception) { + } catch (\Exception $exception) { if (!$this->force) { throw $exception; } @@ -103,6 +77,10 @@ public function handle(Server $server) } } + // clear any allocation notes for the server + $server->allocations()->update(['notes' => null]); + + $server->delete(); }); } diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 66645e4f2f..5397579001 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -14,25 +14,11 @@ class StartupModificationService { use HasUserLevels; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $validatorService; - /** * StartupModificationService constructor. - * - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ - public function __construct(ConnectionInterface $connection, VariableValidatorService $validatorService) + public function __construct(private ConnectionInterface $connection, private VariableValidatorService $validatorService) { - $this->connection = $connection; - $this->validatorService = $validatorService; } /** @@ -79,12 +65,12 @@ public function handle(Server $server, array $data): Server /** * Update certain administrative settings for a server in the DB. */ - protected function updateAdministrativeSettings(array $data, Server &$server) + protected function updateAdministrativeSettings(array $data, Server &$server): void { $eggId = Arr::get($data, 'egg_id'); if (is_digit($eggId) && $server->egg_id !== (int) $eggId) { - /** @var \Pterodactyl\Models\Egg $egg */ + /** @var Egg $egg */ $egg = Egg::query()->findOrFail($data['egg_id']); $server = $server->forceFill([ diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 27bc622f5a..eb9237c5db 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -4,7 +4,6 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; @@ -13,42 +12,27 @@ class SuspensionService public const ACTION_SUSPEND = 'suspend'; public const ACTION_UNSUSPEND = 'unsuspend'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - /** * SuspensionService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private DaemonServerRepository $daemonServerRepository, ) { - $this->connection = $connection; - $this->daemonServerRepository = $daemonServerRepository; } /** * Suspends a server on the system. * - * @param string $action - * * @throws \Throwable */ - public function toggle(Server $server, $action = self::ACTION_SUSPEND) + public function toggle(Server $server, string $action = self::ACTION_SUSPEND): void { Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]); $isSuspending = $action === self::ACTION_SUSPEND; - // Nothing needs to happen if we're suspending the server and it is already + // Nothing needs to happen if we're suspending the server, and it is already // suspended in the database. Additionally, nothing needs to happen if the server - // is not suspended and we try to un-suspend the instance. + // is not suspended, and we try to un-suspend the instance. if ($isSuspending === $server->isSuspended()) { return; } diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php deleted file mode 100644 index 35aed0659f..0000000000 --- a/app/Services/Servers/TransferService.php +++ /dev/null @@ -1,43 +0,0 @@ -repository = $repository; - $this->daemonServerRepository = $daemonServerRepository; - } - - /** - * Requests an archive from the daemon. - * - * @param int|\Pterodactyl\Models\Server $server - * - * @throws \Throwable - */ - public function requestArchive(Server $server) - { - $this->daemonServerRepository->setServer($server)->requestArchive(); - } -} diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 2ac3879a51..676a84aba5 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; @@ -20,29 +13,23 @@ class VariableValidatorService { use HasUserLevels; - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableValidatorService constructor. */ - public function __construct(ValidationFactory $validator) + public function __construct(private ValidationFactory $validator) { - $this->validator = $validator; } /** * Validate all of the passed data against the given service option variables. * - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException */ public function handle(int $egg, array $fields = []): Collection { $query = EggVariable::query()->where('egg_id', $egg); if (!$this->isUserLevel(User::USER_LEVEL_ADMIN)) { - // Don't attempt to validate variables if they aren't user editable + // Don't attempt to validate variables if they aren't user editable, // and we're not running this at an admin level. $query = $query->where('user_editable', true)->where('user_viewable', true); } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index b1affd3f38..f77393d63d 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -15,39 +15,15 @@ class SubuserCreationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $subuserRepository; - - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - private $userCreationService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $userRepository; - /** * SubuserCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - SubuserRepository $subuserRepository, - UserCreationService $userCreationService, - UserRepositoryInterface $userRepository + private ConnectionInterface $connection, + private SubuserRepository $subuserRepository, + private UserCreationService $userCreationService, + private UserRepositoryInterface $userRepository, ) { - $this->connection = $connection; - $this->subuserRepository = $subuserRepository; - $this->userRepository = $userRepository; - $this->userCreationService = $userCreationService; } /** @@ -56,8 +32,8 @@ public function __construct( * be created. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException - * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws ServerSubuserExistsException + * @throws UserIsServerOwnerException * @throws \Throwable */ public function handle(Server $server, string $email, array $permissions): Subuser @@ -74,7 +50,7 @@ public function handle(Server $server, string $email, array $permissions): Subus if ($subuserCount !== 0) { throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); } - } catch (RecordNotFoundException $exception) { + } catch (RecordNotFoundException) { // Just cap the username generated at 64 characters at most and then append a random string // to the end to make it "unique"... $username = substr(preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')), 0, 64) . Str::random(3); diff --git a/app/Services/Telemetry/TelemetryCollectionService.php b/app/Services/Telemetry/TelemetryCollectionService.php new file mode 100644 index 0000000000..1ece481410 --- /dev/null +++ b/app/Services/Telemetry/TelemetryCollectionService.php @@ -0,0 +1,186 @@ +collect(); + } catch (\Exception) { + return; + } + + Http::post('https://telemetry.pterodactyl.io', $data); + } + + /** + * Collects telemetry data and returns it as an array. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function collect(): array + { + $uuid = $this->settingsRepository->get('app:telemetry:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $this->settingsRepository->set('app:telemetry:uuid', $uuid); + } + + $nodes = Node::all()->map(function ($node) { + try { + $info = $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(2); + } catch (\Exception) { + return null; + } + + return [ + 'id' => $node->uuid, + 'version' => Arr::get($info, 'version', ''), + + 'docker' => [ + 'version' => Arr::get($info, 'docker.version', ''), + + 'cgroups' => [ + 'driver' => Arr::get($info, 'docker.cgroups.driver', ''), + 'version' => Arr::get($info, 'docker.cgroups.version', ''), + ], + + 'containers' => [ + 'total' => Arr::get($info, 'docker.containers.total', -1), + 'running' => Arr::get($info, 'docker.containers.running', -1), + 'paused' => Arr::get($info, 'docker.containers.paused', -1), + 'stopped' => Arr::get($info, 'docker.containers.stopped', -1), + ], + + 'storage' => [ + 'driver' => Arr::get($info, 'docker.storage.driver', ''), + 'filesystem' => Arr::get($info, 'docker.storage.filesystem', ''), + ], + + 'runc' => [ + 'version' => Arr::get($info, 'docker.runc.version', ''), + ], + ], + + 'system' => [ + 'architecture' => Arr::get($info, 'system.architecture', ''), + 'cpuThreads' => Arr::get($info, 'system.cpu_threads', ''), + 'memoryBytes' => Arr::get($info, 'system.memory_bytes', ''), + 'kernelVersion' => Arr::get($info, 'system.kernel_version', ''), + 'os' => Arr::get($info, 'system.os', ''), + 'osType' => Arr::get($info, 'system.os_type', ''), + ], + ]; + })->filter(fn ($node) => !is_null($node))->toArray(); + + return [ + 'id' => $uuid, + + 'panel' => [ + 'version' => config('app.version'), + 'phpVersion' => phpversion(), + + 'drivers' => [ + 'backup' => [ + 'type' => config('backups.default'), + ], + + 'cache' => [ + 'type' => config('cache.default'), + ], + + 'database' => [ + 'type' => config('database.default'), + 'version' => DB::getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION), + ], + ], + ], + + 'resources' => [ + 'allocations' => [ + 'count' => Allocation::count(), + 'used' => Allocation::whereNotNull('server_id')->count(), + ], + + 'backups' => [ + 'count' => Backup::count(), + 'bytes' => Backup::sum('bytes'), + ], + + 'eggs' => [ + 'count' => Egg::count(), + // Egg UUIDs are generated randomly on import, so there is not a consistent way to + // determine if servers are using default eggs or not. + // 'server_usage' => Egg::all() + // ->flatMap(fn (Egg $egg) => [$egg->uuid => $egg->servers->count()]) + // ->filter(fn (int $count) => $count > 0) + // ->toArray(), + ], + + 'locations' => [ + 'count' => Location::count(), + ], + + 'mounts' => [ + 'count' => Mount::count(), + ], + + 'nests' => [ + 'count' => Nest::count(), + // Nest UUIDs are generated randomly on import, so there is not a consistent way to + // determine if servers are using default eggs or not. + // 'server_usage' => Nest::all() + // ->flatMap(fn (Nest $nest) => [$nest->uuid => $nest->eggs->sum(fn (Egg $egg) => $egg->servers->count())]) + // ->filter(fn (int $count) => $count > 0) + // ->toArray(), + ], + + 'nodes' => [ + 'count' => Node::count(), + ], + + 'servers' => [ + 'count' => Server::count(), + 'suspended' => Server::where('status', Server::STATUS_SUSPENDED)->count(), + ], + + 'users' => [ + 'count' => User::count(), + 'admins' => User::where('root_admin', true)->count(), + ], + ], + + 'nodes' => $nodes, + ]; + } +} diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index 28faff7f1b..8ed71eb1fc 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -14,60 +14,28 @@ class ToggleTwoFactorService { - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \PragmaRX\Google2FA\Google2FA - */ - private $google2FA; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository - */ - private $recoveryTokenRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - /** * ToggleTwoFactorService constructor. */ public function __construct( - ConnectionInterface $connection, - Encrypter $encrypter, - Google2FA $google2FA, - RecoveryTokenRepository $recoveryTokenRepository, - UserRepositoryInterface $repository + private ConnectionInterface $connection, + private Encrypter $encrypter, + private Google2FA $google2FA, + private RecoveryTokenRepository $recoveryTokenRepository, + private UserRepositoryInterface $repository, ) { - $this->encrypter = $encrypter; - $this->google2FA = $google2FA; - $this->repository = $repository; - $this->recoveryTokenRepository = $recoveryTokenRepository; - $this->connection = $connection; } /** * Toggle 2FA on an account only if the token provided is valid. * - * @return string[] - * * @throws \Throwable * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException - * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + * @throws TwoFactorAuthenticationTokenInvalid */ - public function handle(User $user, string $token, bool $toggleState = null): array + public function handle(User $user, string $token, ?bool $toggleState = null): array { $secret = $this->encrypter->decrypt($user->totp_secret); @@ -111,7 +79,7 @@ public function handle(User $user, string $token, bool $toggleState = null): arr } $this->repository->withoutFreshModel()->update($user->id, [ - 'totp_authenticated_at' => Carbon::now(), + 'totp_authenticated_at' => null, 'use_totp' => (is_null($toggleState) ? !$user->use_totp : $toggleState), ]); diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index be234d4678..c48a99dbdb 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Services\Users; -use Exception; -use RuntimeException; use Pterodactyl\Models\User; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -13,32 +11,14 @@ class TwoFactorSetupService { public const VALID_BASE32_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - /** * TwoFactorSetupService constructor. */ public function __construct( - ConfigRepository $config, - Encrypter $encrypter, - UserRepositoryInterface $repository + private ConfigRepository $config, + private Encrypter $encrypter, + private UserRepositoryInterface $repository, ) { - $this->config = $config; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** @@ -56,8 +36,8 @@ public function handle(User $user): array for ($i = 0; $i < $this->config->get('pterodactyl.auth.2fa.bytes', 16); ++$i) { $secret .= substr(self::VALID_BASE32_CHARACTERS, random_int(0, 31), 1); } - } catch (Exception $exception) { - throw new RuntimeException($exception->getMessage(), 0, $exception); + } catch (\Exception $exception) { + throw new \RuntimeException($exception->getMessage(), 0, $exception); } $this->repository->withoutFreshModel()->update($user->id, [ diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php index 2d0abcb9fa..7fe8bd7954 100644 --- a/app/Services/Users/UserCreationService.php +++ b/app/Services/Users/UserCreationService.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Services\Users; use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\User; +use Pterodactyl\Facades\Activity; use Illuminate\Contracts\Hashing\Hasher; use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Auth\PasswordBroker; @@ -12,49 +14,23 @@ class UserCreationService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Contracts\Hashing\Hasher - */ - private $hasher; - - /** - * @var \Illuminate\Contracts\Auth\PasswordBroker - */ - private $passwordBroker; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * CreationService constructor. + * UserCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - Hasher $hasher, - PasswordBroker $passwordBroker, - UserRepositoryInterface $repository + private ConnectionInterface $connection, + private Hasher $hasher, + private PasswordBroker $passwordBroker, + private UserRepositoryInterface $repository, ) { - $this->connection = $connection; - $this->hasher = $hasher; - $this->passwordBroker = $passwordBroker; - $this->repository = $repository; } /** * Create a new user on the system. * - * @return \Pterodactyl\Models\User - * * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): User { if (array_key_exists('password', $data) && !empty($data['password'])) { $data['password'] = $this->hasher->make($data['password']); @@ -66,7 +42,7 @@ public function handle(array $data) $data['password'] = $this->hasher->make(str_random(30)); } - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = $this->repository->create(array_merge($data, [ 'uuid' => Uuid::uuid4()->toString(), ]), true, true); @@ -78,6 +54,15 @@ public function handle(array $data) $this->connection->commit(); $user->notify(new AccountCreated($user, $token ?? null)); + Activity::event('user:user.create') + ->subject($user) + ->property([ + 'email' => $user->email, + 'username' => $user->username, + 'admin' => $user->root_admin, + ]) + ->log(); + return $user; } } diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php index fe02bc72c9..b58d89d4c1 100644 --- a/app/Services/Users/UserDeletionService.php +++ b/app/Services/Users/UserDeletionService.php @@ -1,71 +1,27 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - namespace Pterodactyl\Services\Users; use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Translation\Translator; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class UserDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * DeletionService constructor. - */ - public function __construct( - ServerRepositoryInterface $serverRepository, - Translator $translator, - UserRepositoryInterface $repository - ) { - $this->repository = $repository; - $this->translator = $translator; - $this->serverRepository = $serverRepository; - } - /** * Delete a user from the panel only if they have no servers attached to their account. * - * @param int|\Pterodactyl\Models\User $user - * - * @return bool|null - * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws DisplayException */ - public function handle($user) + public function handle(int|User $user): ?bool { - if ($user instanceof User) { - $user = $user->id; - } + $user = $user instanceof User ? $user : User::query()->findOrFail($user); - $servers = $this->serverRepository->setColumns('id')->findCountWhere([['owner_id', '=', $user]]); + $servers = Server::query()->where('owner_id', $user->id)->count(); if ($servers > 0) { - throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers')); + throw new DisplayException(trans('admin/user.exceptions.user_has_servers')); } - return $this->repository->delete($user); + return $user->delete(); } } diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php index 31f4010b65..83846040e9 100644 --- a/app/Services/Users/UserUpdateService.php +++ b/app/Services/Users/UserUpdateService.php @@ -4,6 +4,7 @@ use Pterodactyl\Models\User; use Illuminate\Contracts\Hashing\Hasher; +use Pterodactyl\Events\User\PasswordChanged; use Pterodactyl\Traits\Services\HasUserLevels; class UserUpdateService @@ -11,16 +12,10 @@ class UserUpdateService use HasUserLevels; /** - * @var \Illuminate\Contracts\Hashing\Hasher + * UserUpdateService constructor. */ - private $hasher; - - /** - * UpdateService constructor. - */ - public function __construct(Hasher $hasher) + public function __construct(private Hasher $hasher) { - $this->hasher = $hasher; } /** @@ -38,6 +33,10 @@ public function handle(User $user, array $data): User $user->forceFill($data)->saveOrFail(); + if (isset($data['password'])) { + PasswordChanged::dispatch($user); + } + return $user->refresh(); } } diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php index 5435dc3214..8d6681623a 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -8,11 +8,15 @@ trait EnvironmentWriterTrait { /** * Escapes an environment value by looking for any characters that could - * reasonablly cause environment parsing issues. Those values are then wrapped + * reasonably cause environment parsing issues. Those values are then wrapped * in quotes before being returned. */ - public function escapeEnvironmentValue(string $value): string + public function escapeEnvironmentValue(?string $value): string { + if (is_null($value)) { + return ''; + } + if (!preg_match('/^\"(.*)\"$/', $value) && preg_match('/([^\w.\-+\/])+/', $value)) { return sprintf('"%s"', addslashes($value)); } @@ -23,9 +27,9 @@ public function escapeEnvironmentValue(string $value): string /** * Update the .env file for the application using the passed in values. * - * @throws \Pterodactyl\Exceptions\PterodactylException + * @throws PterodactylException */ - public function writeToEnvironment(array $values = []) + public function writeToEnvironment(array $values = []): void { $path = base_path('.env'); if (!file_exists($path)) { diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php index 4e7e0a1205..44fec983ac 100644 --- a/app/Traits/Controllers/JavascriptInjection.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -1,30 +1,17 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Traits\Controllers; -use Javascript; use Illuminate\Http\Request; trait JavascriptInjection { - /** - * @var \Illuminate\Http\Request - */ - private $request; + private Request $request; /** * Set the request object to use when injecting JS. - * - * @return $this */ - public function setRequest(Request $request) + public function setRequest(Request $request): self { $this->request = $request; @@ -33,13 +20,9 @@ public function setRequest(Request $request) /** * Injects the exact array passed in, nothing more. - * - * @param array $args - * - * @return array */ - public function plainInject($args = []) + public function plainInject(array $args = []): string { - return Javascript::put($args); + return \JavaScript::put($args); } } diff --git a/app/Traits/Controllers/PlainJavascriptInjection.php b/app/Traits/Controllers/PlainJavascriptInjection.php deleted file mode 100644 index f514eb1af6..0000000000 --- a/app/Traits/Controllers/PlainJavascriptInjection.php +++ /dev/null @@ -1,22 +0,0 @@ -getFilesystemInstance()->directories(resource_path('lang')))->mapWithKeys(function ($path) use ($localize) { $code = basename($path); diff --git a/app/Traits/Services/HasUserLevels.php b/app/Traits/Services/HasUserLevels.php index 293e7fce74..33be0fbf1a 100644 --- a/app/Traits/Services/HasUserLevels.php +++ b/app/Traits/Services/HasUserLevels.php @@ -6,17 +6,12 @@ trait HasUserLevels { - /** - * @var int - */ - private $userLevel = User::USER_LEVEL_USER; + private int $userLevel = User::USER_LEVEL_USER; /** * Set the access level for running this function. - * - * @return $this */ - public function setUserLevel(int $level) + public function setUserLevel(int $level): self { $this->userLevel = $level; diff --git a/app/Traits/Services/ReturnsUpdatedModels.php b/app/Traits/Services/ReturnsUpdatedModels.php index 07e4f4fde3..055e8971ed 100644 --- a/app/Traits/Services/ReturnsUpdatedModels.php +++ b/app/Traits/Services/ReturnsUpdatedModels.php @@ -4,15 +4,9 @@ trait ReturnsUpdatedModels { - /** - * @var bool - */ - private $updatedModel = false; + private bool $updatedModel = false; - /** - * @return bool - */ - public function getUpdatedModel() + public function getUpdatedModel(): bool { return $this->updatedModel; } @@ -21,10 +15,8 @@ public function getUpdatedModel() * If called a fresh model will be returned from the database. This is used * for API calls, but is unnecessary for UI based updates where the page is * being reloaded and a fresh model will be pulled anyways. - * - * @return $this */ - public function returnUpdatedModel(bool $toggle = true) + public function returnUpdatedModel(bool $toggle = true): self { $this->updatedModel = $toggle; diff --git a/app/Traits/Services/ValidatesValidationRules.php b/app/Traits/Services/ValidatesValidationRules.php index f4c1ea1881..115649b0da 100644 --- a/app/Traits/Services/ValidatesValidationRules.php +++ b/app/Traits/Services/ValidatesValidationRules.php @@ -2,28 +2,25 @@ namespace Pterodactyl\Traits\Services; -use BadMethodCallException; use Illuminate\Support\Str; -use Illuminate\Contracts\Validation\Factory; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException; trait ValidatesValidationRules { - abstract protected function getValidator(): Factory; + abstract protected function getValidator(): ValidationFactory; /** * Validate that the rules being provided are valid for Laravel and can * be resolved. * - * @param array|string $rules - * - * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException + * @throws BadValidationRuleException */ - public function validateRules($rules) + public function validateRules(array|string $rules): void { try { $this->getValidator()->make(['__TEST' => 'test'], ['__TEST' => $rules])->fails(); - } catch (BadMethodCallException $exception) { + } catch (\BadMethodCallException $exception) { $matches = []; if (preg_match('/Method \[(.+)\] does not exist\./', $exception->getMessage(), $matches)) { throw new BadValidationRuleException(trans('exceptions.nest.variables.bad_validation_rule', ['rule' => Str::snake(str_replace('validate', '', array_get($matches, 1, 'unknownRule')))]), $exception); diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index 1352947717..fcd65f98f0 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -4,17 +4,17 @@ use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class AllocationTransformer extends BaseTransformer { /** * Relationships that can be loaded onto allocation transformations. - * - * @var array */ - protected $availableIncludes = ['node', 'server']; + protected array $availableIncludes = ['node', 'server']; /** * Return the resource name for the JSONAPI output. @@ -26,10 +26,8 @@ public function getResourceName(): string /** * Return a generic transformed allocation array. - * - * @return array */ - public function transform(Allocation $allocation) + public function transform(Allocation $allocation): array { return [ 'id' => $allocation->id, @@ -44,11 +42,9 @@ public function transform(Allocation $allocation) /** * Load the node relationship onto a given transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNode(Allocation $allocation) + public function includeNode(Allocation $allocation): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); @@ -64,11 +60,9 @@ public function includeNode(Allocation $allocation) /** * Load the server relationship onto a given transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServer(Allocation $allocation) + public function includeServer(Allocation $allocation): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS) || !$allocation->server) { return $this->null(); diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php index 3c5630366d..a74f32fcec 100644 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ b/app/Transformers/Api/Application/BaseTransformer.php @@ -3,12 +3,14 @@ namespace Pterodactyl\Transformers\Api\Application; use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Illuminate\Http\Request; +use Webmozart\Assert\Assert; use Pterodactyl\Models\ApiKey; use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Model; use League\Fractal\TransformerAbstract; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; /** * @method array transform(Model $model) @@ -17,15 +19,7 @@ abstract class BaseTransformer extends TransformerAbstract { public const RESPONSE_TIMEZONE = 'UTC'; - /** - * @var \Pterodactyl\Models\ApiKey - */ - private $key; - - /** - * Return the resource name for the JSONAPI output. - */ - abstract public function getResourceName(): string; + protected Request $request; /** * BaseTransformer constructor. @@ -39,54 +33,73 @@ public function __construct() } /** - * Set the HTTP request class being used for this request. - * - * @return $this + * Return the resource name for the JSONAPI output. + */ + abstract public function getResourceName(): string; + + /** + * Sets the request on the instance. */ - public function setKey(ApiKey $key) + public function setRequest(Request $request): self { - $this->key = $key; + $this->request = $request; return $this; } /** - * Return the request instance being used for this transformer. + * Returns a new transformer instance with the request set on the instance. */ - public function getKey(): ApiKey + public static function fromRequest(Request $request): static { - return $this->key; + return app(static::class)->setRequest($request); } /** * Determine if the API key loaded onto the transformer has permission * to access a different resource. This is used when including other * models on a transformation request. + * + * @deprecated — prefer $user->can/cannot methods */ protected function authorize(string $resource): bool { - return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); + $allowed = [ApiKey::TYPE_ACCOUNT, ApiKey::TYPE_APPLICATION]; + + $token = $this->request->user()?->currentAccessToken(); + if (!$token instanceof ApiKey || !in_array($token->key_type, $allowed)) { + return false; + } + + // If this is not a deprecated application token type we can only check that + // the user is a root admin at the moment. In a future release we'll be rolling + // out more specific permissions for keys. + if ($token->key_type === ApiKey::TYPE_ACCOUNT) { + return $this->request->user()->root_admin; + } + + return AdminAcl::check($token, $resource); } /** * Create a new instance of the transformer and pass along the currently * set API key. * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer + * + * @param class-string $abstract + * + * @return T * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * + * @noinspection PhpDocSignatureInspection */ - protected function makeTransformer(string $abstract, array $parameters = []) + protected function makeTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + Assert::subclassOf($abstract, self::class); // @phpstan-ignore staticMethod.alreadyNarrowedType - return $transformer; + return $abstract::fromRequest($this->request); } /** @@ -94,8 +107,8 @@ protected function makeTransformer(string $abstract, array $parameters = []) */ protected function formatTimestamp(string $timestamp): string { - return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) + return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) ->setTimezone(self::RESPONSE_TIMEZONE) - ->toIso8601String(); + ->toAtomString(); } } diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index be888df5cf..019fdf261d 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -4,14 +4,13 @@ use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class DatabaseHostTransformer extends BaseTransformer { - /** - * @var array - */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'databases', ]; @@ -25,10 +24,8 @@ public function getResourceName(): string /** * Transform database host into a representation for the application API. - * - * @return array */ - public function transform(DatabaseHost $model) + public function transform(DatabaseHost $model): array { return [ 'id' => $model->id, @@ -37,19 +34,17 @@ public function transform(DatabaseHost $model) 'port' => $model->port, 'username' => $model->username, 'node' => $model->node_id, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Include the databases associated with this host. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeDatabases(DatabaseHost $model) + public function includeDatabases(DatabaseHost $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index a1348a0ad4..9ed5736b46 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -2,20 +2,22 @@ namespace Pterodactyl\Transformers\Api\Application; +use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class EggTransformer extends BaseTransformer { /** * Relationships that can be loaded onto this transformation. - * - * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'nest', 'servers', 'config', @@ -35,10 +37,15 @@ public function getResourceName(): string * Transform an Egg model into a representation that can be consumed by * the application api. * - * @return array + * @throws \JsonException */ - public function transform(Egg $model) + public function transform(Egg $model): array { + $files = json_decode($model->config_files, true, 512, JSON_THROW_ON_ERROR); + if (empty($files)) { + $files = new \stdClass(); + } + return [ 'id' => $model->id, 'uuid' => $model->uuid, @@ -49,10 +56,10 @@ public function transform(Egg $model) // "docker_image" is deprecated, but left here to avoid breaking too many things at once // in external software. We'll remove it down the road once things have gotten the chance // to upgrade to using "docker_images". - 'docker_image' => count($model->docker_images) > 0 ? $model->docker_images[0] : '', + 'docker_image' => count($model->docker_images) > 0 ? Arr::first($model->docker_images) : '', 'docker_images' => $model->docker_images, 'config' => [ - 'files' => json_decode($model->config_files, true), + 'files' => $files, 'startup' => json_decode($model->config_startup, true), 'stop' => $model->config_stop, 'logs' => json_decode($model->config_logs, true), @@ -75,11 +82,9 @@ public function transform(Egg $model) /** * Include the Nest relationship for the given Egg in the transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNest(Egg $model) + public function includeNest(Egg $model): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { return $this->null(); @@ -93,11 +98,9 @@ public function includeNest(Egg $model) /** * Include the Servers relationship for the given Egg in the transformation. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Egg $model) + public function includeServers(Egg $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -111,10 +114,8 @@ public function includeServers(Egg $model) /** * Include more detailed information about the configuration if this Egg is * extending another. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includeConfig(Egg $model) + public function includeConfig(Egg $model): Item|NullResource { if (is_null($model->config_from)) { return $this->null(); @@ -135,10 +136,8 @@ public function includeConfig(Egg $model) /** * Include more detailed information about the script configuration if the * Egg is extending another. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includeScript(Egg $model) + public function includeScript(Egg $model): Item|NullResource { if (is_null($model->copy_script_from)) { return $this->null(); @@ -159,11 +158,9 @@ public function includeScript(Egg $model) /** * Include the variables that are defined for this Egg. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Egg $model) + public function includeVariables(Egg $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index dfa8315ec8..8fea3feb31 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -3,16 +3,16 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Location; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class LocationTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = ['nodes', 'servers']; + protected array $availableIncludes = ['nodes', 'servers']; /** * Return the resource name for the JSONAPI output. @@ -39,11 +39,9 @@ public function transform(Location $location): array /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Location $location) + public function includeServers(Location $location): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -57,11 +55,9 @@ public function includeServers(Location $location) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNodes(Location $location) + public function includeNodes(Location $location): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 506b0276b6..2f530d44e4 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -5,16 +5,16 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class NestTransformer extends BaseTransformer { /** * Relationships that can be loaded onto this transformation. - * - * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'eggs', 'servers', ]; @@ -29,10 +29,8 @@ public function getResourceName(): string /** * Transform a Nest model into a representation that can be consumed by the * application API. - * - * @return array */ - public function transform(Nest $model) + public function transform(Nest $model): array { $response = $model->toArray(); @@ -45,11 +43,9 @@ public function transform(Nest $model) /** * Include the Eggs relationship on the given Nest model transformation. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEggs(Nest $model) + public function includeEggs(Nest $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); @@ -63,11 +59,9 @@ public function includeEggs(Nest $model) /** * Include the servers relationship on the given Nest model. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Nest $model) + public function includeServers(Nest $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index aaed68e9b8..6347dfec39 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -3,16 +3,17 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Node; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class NodeTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = ['allocations', 'location', 'servers']; + protected array $availableIncludes = ['allocations', 'location', 'servers']; /** * Return the resource name for the JSONAPI output. @@ -52,11 +53,9 @@ public function transform(Node $node): array /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Node $node) + public function includeAllocations(Node $node): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { return $this->null(); @@ -74,11 +73,9 @@ public function includeAllocations(Node $node) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeLocation(Node $node) + public function includeLocation(Node $node): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { return $this->null(); @@ -96,11 +93,9 @@ public function includeLocation(Node $node) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Node $node) + public function includeServers(Node $node): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 95180e2d9a..2590482d92 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -3,21 +3,17 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Database; +use League\Fractal\Resource\Item; use Pterodactyl\Models\DatabaseHost; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Contracts\Encryption\Encrypter; class ServerDatabaseTransformer extends BaseTransformer { - /** - * @var array - */ - protected $availableIncludes = ['password', 'host']; + protected array $availableIncludes = ['password', 'host']; - /** - * @var Encrypter - */ - private $encrypter; + private Encrypter $encrypter; /** * Perform dependency injection. @@ -48,17 +44,15 @@ public function transform(Database $model): array 'username' => $model->username, 'remote' => $model->remote, 'max_connections' => $model->max_connections, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Include the database password in the request. - * - * @return \League\Fractal\Resource\Item */ - public function includePassword(Database $model) + public function includePassword(Database $model): Item { return $this->item($model, function (Database $model) { return [ @@ -70,11 +64,9 @@ public function includePassword(Database $model) /** * Return the database host relationship for this server database. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeHost(Database $model) + public function includeHost(Database $model): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index f516380226..e5db01fb21 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -3,22 +3,20 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Servers\EnvironmentService; class ServerTransformer extends BaseTransformer { - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; + private EnvironmentService $environmentService; /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'allocations', 'user', 'subusers', @@ -96,11 +94,9 @@ public function transform(Server $server): array /** * Return a generic array of allocations for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Server $server) + public function includeAllocations(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { return $this->null(); @@ -114,11 +110,9 @@ public function includeAllocations(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeSubusers(Server $server) + public function includeSubusers(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -132,11 +126,9 @@ public function includeSubusers(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeUser(Server $server) + public function includeUser(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -150,11 +142,9 @@ public function includeUser(Server $server) /** * Return a generic array with nest information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNest(Server $server) + public function includeNest(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { return $this->null(); @@ -168,11 +158,9 @@ public function includeNest(Server $server) /** * Return a generic array with egg information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEgg(Server $server) + public function includeEgg(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); @@ -186,11 +174,9 @@ public function includeEgg(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Server $server) + public function includeVariables(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -204,11 +190,9 @@ public function includeVariables(Server $server) /** * Return a generic array with location information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeLocation(Server $server) + public function includeLocation(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { return $this->null(); @@ -222,11 +206,9 @@ public function includeLocation(Server $server) /** * Return a generic array with node information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNode(Server $server) + public function includeNode(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); @@ -240,11 +222,9 @@ public function includeNode(Server $server) /** * Return a generic array with database information for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeDatabases(Server $server) + public function includeDatabases(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index aefa318e26..e27d1e0135 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -2,17 +2,18 @@ namespace Pterodactyl\Transformers\Api\Application; +use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Models\ServerVariable; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class ServerVariableTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = ['parent']; + protected array $availableIncludes = ['parent']; /** * Return the resource name for the JSONAPI output. @@ -24,10 +25,8 @@ public function getResourceName(): string /** * Return a generic transformed server variable array. - * - * @return array */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { return $variable->toArray(); } @@ -35,11 +34,9 @@ public function transform(EggVariable $variable) /** * Return the parent service variable data. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeParent(EggVariable $variable) + public function includeParent(EggVariable $variable): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 08c6226666..0a51d61d99 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -3,16 +3,16 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Subuser; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class SubuserTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = ['user', 'server']; + protected array $availableIncludes = ['user', 'server']; /** * Return the resource name for the JSONAPI output. @@ -40,11 +40,9 @@ public function transform(Subuser $subuser): array /** * Return a generic item of user for this subuser. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeUser(Subuser $subuser) + public function includeUser(Subuser $subuser): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -58,11 +56,9 @@ public function includeUser(Subuser $subuser) /** * Return a generic item of server for this subuser. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServer(Subuser $subuser) + public function includeServer(Subuser $subuser): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index b3b4235079..14e354b45d 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -3,16 +3,16 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\User; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class UserTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ - protected $availableIncludes = ['servers']; + protected array $availableIncludes = ['servers']; /** * Return the resource name for the JSONAPI output. @@ -46,11 +46,9 @@ public function transform(User $user): array /** * Return the servers associated with this user. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(User $user) + public function includeServers(User $user): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 405a4208a7..1a14555d83 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -15,11 +15,9 @@ public function getResourceName(): string } /** - * Return basic information about the currently logged in user. - * - * @return array + * Return basic information about the currently logged-in user. */ - public function transform(User $model) + public function transform(User $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php new file mode 100644 index 0000000000..43b11c229a --- /dev/null +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -0,0 +1,118 @@ + sha1($model->id), + 'batch' => $model->batch, + 'event' => $model->event, + 'is_api' => !is_null($model->api_key_id), + 'ip' => $this->canViewIP($model->actor) ? $model->ip : null, + 'description' => $model->description, + 'properties' => $this->properties($model), + 'has_additional_metadata' => $this->hasAdditionalMetadata($model), + 'timestamp' => $model->timestamp->toAtomString(), + ]; + } + + public function includeActor(ActivityLog $model) + { + if (!$model->actor instanceof User) { + return $this->null(); + } + + return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + } + + /** + * Transforms any array values in the properties into a countable field for easier + * use within the translation outputs. + */ + protected function properties(ActivityLog $model): object + { + if (!$model->properties || $model->properties->isEmpty()) { + return (object) []; + } + + $properties = $model->properties + ->mapWithKeys(function ($value, $key) use ($model) { + if ($key === 'ip' && !optional($model->actor)->is($this->request->user())) { + return [$key => '[hidden]']; + } + + if (!is_array($value)) { + // Perform some directory normalization at this point. + if ($key === 'directory') { + $value = str_replace('//', '/', '/' . trim($value, '/') . '/'); + } + + return [$key => $value]; + } + + return [$key => $value, "{$key}_count" => count($value)]; + }); + + $keys = $properties->keys()->filter(fn ($key) => Str::endsWith($key, '_count'))->values(); + if ($keys->containsOneItem()) { + $properties = $properties->merge(['count' => $properties->get($keys[0])])->except($keys[0]); + } + + return (object) $properties->toArray(); + } + + /** + * Determines if there are any log properties that we've not already exposed + * in the response language string and that are not just the IP address or + * the browser useragent. + * + * This is used by the front-end to selectively display an "additional metadata" + * button that is pointless if there is nothing the user can't already see from + * the event description. + */ + protected function hasAdditionalMetadata(ActivityLog $model): bool + { + if (is_null($model->properties) || $model->properties->isEmpty()) { + return false; + } + + $str = trans('activity.' . str_replace(':', '.', $model->event)); + preg_match_all('/:(?[\w.-]+\w)(?:[^\w:]?|$)/', $str, $matches); + + $exclude = array_merge($matches['key'], ['ip', 'useragent', 'using_sftp']); + foreach ($model->properties->keys() as $key) { + if (!in_array($key, $exclude, true)) { + return true; + } + } + + return false; + } + + /** + * Determines if the user can view the IP address in the output either because they are the + * actor that performed the action, or because they are an administrator on the Panel. + */ + protected function canViewIP(?Model $actor = null): bool + { + return optional($actor)->is($this->request->user()) || $this->request->user()->root_admin; + } +} diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php index 0d9bfec7c8..2e63e2bc9c 100644 --- a/app/Transformers/Api/Client/AllocationTransformer.php +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -14,12 +14,7 @@ public function getResourceName(): string return 'allocation'; } - /** - * Return basic information about the currently logged in user. - * - * @return array - */ - public function transform(Allocation $model) + public function transform(Allocation $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php index c7c39c2b45..ee6efae219 100644 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ b/app/Transformers/Api/Client/ApiKeyTransformer.php @@ -6,9 +6,6 @@ class ApiKeyTransformer extends BaseClientTransformer { - /** - * {@inheritdoc} - */ public function getResourceName(): string { return ApiKey::RESOURCE_NAME; @@ -16,17 +13,15 @@ public function getResourceName(): string /** * Transform this model into a representation that can be consumed by a client. - * - * @return array */ - public function transform(ApiKey $model) + public function transform(ApiKey $model): array { return [ 'identifier' => $model->identifier, 'description' => $model->memo, 'allowed_ips' => $model->allowed_ips, - 'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), + 'last_used_at' => $model->last_used_at ? $model->last_used_at->toAtomString() : null, + 'created_at' => $model->created_at->toAtomString(), ]; } } diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index ae6ae52132..298c996427 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -11,10 +11,7 @@ public function getResourceName(): string return Backup::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Backup $backup) + public function transform(Backup $backup): array { return [ 'uuid' => $backup->uuid, @@ -24,8 +21,8 @@ public function transform(Backup $backup) 'ignored_files' => $backup->ignored_files, 'checksum' => $backup->checksum, 'bytes' => $backup->bytes, - 'created_at' => $backup->created_at->toIso8601String(), - 'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null, + 'created_at' => $backup->created_at->toAtomString(), + 'completed_at' => $backup->completed_at ? $backup->completed_at->toAtomString() : null, ]; } } diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php index efe53276f3..8e76629f70 100644 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ b/app/Transformers/Api/Client/BaseClientTransformer.php @@ -5,31 +5,16 @@ use Pterodactyl\Models\User; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Illuminate\Container\Container; -use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; use Pterodactyl\Transformers\Api\Application\BaseTransformer as BaseApplicationTransformer; abstract class BaseClientTransformer extends BaseApplicationTransformer { - /** - * @var \Pterodactyl\Models\User - */ - private $user; - /** * Return the user model of the user requesting this transformation. */ public function getUser(): User { - return $this->user; - } - - /** - * Set the user model of the user requesting this transformation. - */ - public function setUser(User $user) - { - $this->user = $user; + return $this->request->user(); } /** @@ -37,33 +22,19 @@ public function setUser(User $user) * to access a different resource. This is used when including other * models on a transformation request. * - * @param \Pterodactyl\Models\Server $server + * @noinspection PhpParameterNameChangedDuringInheritanceInspection */ - protected function authorize(string $ability, Server $server = null): bool + protected function authorize(string $ability, ?Server $server = null): bool { Assert::isInstanceOf($server, Server::class); - return $this->getUser()->can($ability, [$server]); + return $this->request->user()->can($ability, [$server]); } - /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @return self - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - protected function makeTransformer(string $abstract, array $parameters = []) + protected function makeTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + Assert::subclassOf($abstract, self::class); - return $transformer; + return parent::makeTransformer($abstract); } } diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 9c5bd86d4f..23e9666377 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -3,23 +3,19 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Database; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Permission; +use League\Fractal\Resource\NullResource; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Extensions\HashidsInterface; class DatabaseTransformer extends BaseClientTransformer { - protected $availableIncludes = ['password']; + protected array $availableIncludes = ['password']; - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; + private Encrypter $encrypter; - /** - * @var \Pterodactyl\Contracts\Extensions\HashidsInterface - */ - private $hashids; + private HashidsInterface $hashids; /** * Handle dependency injection. @@ -54,12 +50,10 @@ public function transform(Database $model): array /** * Include the database password in the request. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includePassword(Database $database) + public function includePassword(Database $database): Item|NullResource { - if (!$this->getUser()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + if (!$this->request->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php index 25e1350072..8e2e3474e3 100644 --- a/app/Transformers/Api/Client/EggTransformer.php +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -14,10 +14,7 @@ public function getResourceName(): string return Egg::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Egg $egg) + public function transform(Egg $egg): array { return [ 'uuid' => $egg->uuid, diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index c7725b9783..09c344249b 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Transformers\Api\Client; -use BadMethodCallException; use Pterodactyl\Models\EggVariable; class EggVariableTransformer extends BaseClientTransformer @@ -12,16 +11,13 @@ public function getResourceName(): string return EggVariable::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { // This guards against someone incorrectly retrieving variables (haha, me) and then passing // them into the transformer and along to the user. Just throw an exception and break the entire // pathway since you should never be exposing these types of variables to a client. if (!$variable->user_viewable) { - throw new BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.'); + throw new \BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.'); } return [ diff --git a/app/Transformers/Daemon/FileObjectTransformer.php b/app/Transformers/Api/Client/FileObjectTransformer.php similarity index 69% rename from app/Transformers/Daemon/FileObjectTransformer.php rename to app/Transformers/Api/Client/FileObjectTransformer.php index 1ad86dce86..6278dad735 100644 --- a/app/Transformers/Daemon/FileObjectTransformer.php +++ b/app/Transformers/Api/Client/FileObjectTransformer.php @@ -1,25 +1,16 @@ Arr::get($item, 'name'), @@ -29,8 +20,8 @@ public function transform(array $item) 'is_file' => Arr::get($item, 'file', true), 'is_symlink' => Arr::get($item, 'symlink', false), 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), - 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toIso8601String(), - 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toIso8601String(), + 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toAtomString(), + 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toAtomString(), ]; } diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 25ef9fb583..e98dc7bc85 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -4,23 +4,14 @@ use Pterodactyl\Models\Task; use Pterodactyl\Models\Schedule; -use Illuminate\Database\Eloquent\Model; +use League\Fractal\Resource\Collection; class ScheduleTransformer extends BaseClientTransformer { - /** - * @var array - */ - protected $availableIncludes = ['tasks']; + protected array $availableIncludes = ['tasks']; - /** - * @var array - */ - protected $defaultIncludes = ['tasks']; + protected array $defaultIncludes = ['tasks']; - /** - * {@inheritdoc} - */ public function getResourceName(): string { return Schedule::RESOURCE_NAME; @@ -28,10 +19,8 @@ public function getResourceName(): string /** * Returns a transformed schedule model such that a client can view the information. - * - * @return array */ - public function transform(Schedule $model) + public function transform(Schedule $model): array { return [ 'id' => $model->id, @@ -46,21 +35,19 @@ public function transform(Schedule $model) 'is_active' => $model->is_active, 'is_processing' => $model->is_processing, 'only_when_online' => $model->only_when_online, - 'last_run_at' => $model->last_run_at ? $model->last_run_at->toIso8601String() : null, - 'next_run_at' => $model->next_run_at ? $model->next_run_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'last_run_at' => $model->last_run_at?->toAtomString(), + 'next_run_at' => $model->next_run_at?->toAtomString(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Allows attaching the tasks specific to the schedule in the response. * - * @return \League\Fractal\Resource\Collection - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeTasks(Schedule $model) + public function includeTasks(Schedule $model): Collection { return $this->collection( $model->tasks, diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index c8c10a9b2b..747d2f2d82 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -5,23 +5,20 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; use Illuminate\Container\Container; use Pterodactyl\Models\EggVariable; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Servers\StartupCommandService; class ServerTransformer extends BaseClientTransformer { - /** - * @var string[] - */ - protected $defaultIncludes = ['allocations', 'variables']; + protected array $defaultIncludes = ['allocations', 'variables']; - /** - * @var array - */ - protected $availableIncludes = ['egg', 'subusers']; + protected array $availableIncludes = ['egg', 'subusers']; public function getResourceName(): string { @@ -34,16 +31,26 @@ public function getResourceName(): string */ public function transform(Server $server): array { - /** @var \Pterodactyl\Services\Servers\StartupCommandService $service */ + /** @var StartupCommandService $service */ $service = Container::getInstance()->make(StartupCommandService::class); + $user = $this->request->user(); + return [ - 'server_owner' => $this->getKey()->user_id === $server->owner_id, - 'identifier' => $server->uuidShort, + 'server_owner' => $user->id === $server->owner_id, + 'identifier' => config('pterodactyl.features.new_server_identifiers') + ? $server->identifier + : $server->uuidShort, + '__deprecated_uuid_short' => $server->uuidShort, + // In Pterodactyl 2.0 we'll be replacing `identifier` above with the actual + // "identifier" used internally. This is a completely different value compared + // to the current however, and would be quite a breaking change to URLs. + 'server_identifier' => $server->identifier, 'internal_id' => $server->id, 'uuid' => $server->uuid, 'name' => $server->name, 'node' => $server->node->name, + 'is_node_under_maintenance' => $server->node->isUnderMaintenance(), 'sftp_details' => [ 'ip' => $server->node->fqdn, 'port' => $server->node->daemonSFTP, @@ -58,7 +65,7 @@ public function transform(Server $server): array 'threads' => $server->threads, 'oom_disabled' => $server->oom_disabled, ], - 'invocation' => $service->handle($server, !$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)), + 'invocation' => $service->handle($server, !$user->can(Permission::ACTION_STARTUP_READ, $server)), 'docker_image' => $server->image, 'egg_features' => $server->egg->inherit_features, 'feature_limits' => [ @@ -78,14 +85,13 @@ public function transform(Server $server): array /** * Returns the allocations associated with this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Server $server) + public function includeAllocations(Server $server): Collection { $transformer = $this->makeTransformer(AllocationTransformer::class); + $user = $this->request->user(); // While we include this permission, we do need to actually handle it slightly different here // for the purpose of keeping things functionally working. If the user doesn't have read permissions // for the allocations we'll only return the primary server allocation, and any notes associated @@ -93,7 +99,7 @@ public function includeAllocations(Server $server) // // This allows us to avoid too much permission regression, without also hiding information that // is generally needed for the frontend to make sense when browsing or searching results. - if (!$this->getUser()->can(Permission::ACTION_ALLOCATION_READ, $server)) { + if (!$user->can(Permission::ACTION_ALLOCATION_READ, $server)) { $primary = clone $server->allocation; $primary->notes = null; @@ -104,13 +110,11 @@ public function includeAllocations(Server $server) } /** - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Server $server) + public function includeVariables(Server $server): Collection|NullResource { - if (!$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)) { + if (!$this->request->user()->can(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); } @@ -124,11 +128,9 @@ public function includeVariables(Server $server) /** * Returns the egg associated with this server. * - * @return \League\Fractal\Resource\Item - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEgg(Server $server) + public function includeEgg(Server $server): Item { return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); } @@ -136,13 +138,11 @@ public function includeEgg(Server $server) /** * Returns the subusers associated with this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeSubusers(Server $server) + public function includeSubusers(Server $server): Collection|NullResource { - if (!$this->getUser()->can(Permission::ACTION_USER_READ, $server)) { + if (!$this->request->user()->can(Permission::ACTION_USER_READ, $server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 66827bf363..6b323b3154 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -14,10 +14,8 @@ public function getResourceName(): string /** * Transform stats from the daemon into a result set that can be used in * the client API. - * - * @return array */ - public function transform(array $data) + public function transform(array $data): array { return [ 'current_state' => Arr::get($data, 'state', 'stopped'), diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index 7a8bfde0ed..2902d1f7a2 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -17,11 +17,9 @@ public function getResourceName(): string /** * Transforms a subuser into a model that can be shown to a front-end user. * - * @return array|void - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function transform(Subuser $model) + public function transform(Subuser $model): array { return array_merge( $this->makeTransformer(UserTransformer::class)->transform($model->user), diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index a2e62cf513..25b9eebc8e 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -6,9 +6,6 @@ class TaskTransformer extends BaseClientTransformer { - /** - * {@inheritdoc} - */ public function getResourceName(): string { return Task::RESOURCE_NAME; @@ -16,10 +13,8 @@ public function getResourceName(): string /** * Transforms a schedule's task into a client viewable format. - * - * @return array */ - public function transform(Task $model) + public function transform(Task $model): array { return [ 'id' => $model->id, @@ -29,8 +24,8 @@ public function transform(Task $model) 'time_offset' => $model->time_offset, 'is_queued' => $model->is_queued, 'continue_on_failure' => $model->continue_on_failure, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } } diff --git a/app/Transformers/Api/Client/UserSSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php new file mode 100644 index 0000000000..015a017b69 --- /dev/null +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -0,0 +1,26 @@ + $model->name, + 'fingerprint' => $model->fingerprint, + 'public_key' => $model->public_key, + 'created_at' => $model->created_at->toAtomString(), + ]; + } +} diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 468232f8d6..559aa190d5 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -18,18 +18,17 @@ public function getResourceName(): string /** * Transforms a User model into a representation that can be shown to regular * users of the API. - * - * @return array */ - public function transform(User $model) + public function transform(User $model): array { return [ 'uuid' => $model->uuid, + 'identifier' => $model->identifier, 'username' => $model->username, 'email' => $model->email, 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), '2fa_enabled' => $model->use_totp, - 'created_at' => $model->created_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), ]; } } diff --git a/app/Transformers/Daemon/BaseDaemonTransformer.php b/app/Transformers/Daemon/BaseDaemonTransformer.php deleted file mode 100644 index 7ec7586445..0000000000 --- a/app/Transformers/Daemon/BaseDaemonTransformer.php +++ /dev/null @@ -1,9 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( - $input = new Symfony\Component\Console\Input\ArgvInput, - new Symfony\Component\Console\Output\ConsoleOutput + $input = new Symfony\Component\Console\Input\ArgvInput(), + new Symfony\Component\Console\Output\ConsoleOutput() ); /* diff --git a/babel.config.js b/babel.config.js index a0ead0290d..808bb186f9 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,14 +1,6 @@ -module.exports = { - presets: [ - '@babel/typescript', - ['@babel/env', { - modules: false, - useBuiltIns: 'entry', - corejs: 3, - }], - '@babel/react', - ], - plugins: [ +module.exports = function (api) { + let targets = {}; + const plugins = [ 'babel-plugin-macros', 'styled-components', 'react-hot-loader/babel', @@ -19,5 +11,24 @@ module.exports = { '@babel/proposal-optional-chaining', '@babel/proposal-nullish-coalescing-operator', '@babel/syntax-dynamic-import', - ], + ]; + + if (api.env('test')) { + targets = { node: 'current' }; + plugins.push('@babel/transform-modules-commonjs'); + } + + return { + plugins, + presets: [ + '@babel/typescript', + ['@babel/env', { + modules: false, + useBuiltIns: 'entry', + corejs: 3, + targets, + }], + '@babel/react', + ] + }; }; diff --git a/bootstrap/app.php b/bootstrap/app.php index ee5f1bae2d..74bae64a8f 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -15,6 +15,10 @@ $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); +if (isset($_ENV['APP_STORAGE_PATH'])) { + $app->useStoragePath($_ENV['APP_STORAGE_PATH']); +} + /* |-------------------------------------------------------------------------- | Bind Important Interfaces diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100755 new mode 100644 diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 3503d2ac19..7cab352ecc 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -1,5 +1,6 @@ make(Kernel::class); /* @@ -22,9 +23,10 @@ $output = new ConsoleOutput(); -if (config('database.default') !== 'testing') { +$prefix = 'database.connections.' . config('database.default'); +if (!Str::contains(config("$prefix.database"), 'test')) { $output->writeln(PHP_EOL . 'Cannot run test process against non-testing database.'); - $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config('database.default') . '".'); + $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config("$prefix.database") . '".'); exit(1); } @@ -34,10 +36,10 @@ */ if (!env('SKIP_MIGRATIONS')) { $output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); - $kernel->call('migrate:fresh', ['--database' => 'testing']); + $kernel->call('migrate:fresh'); $output->writeln('Seeding database for Integration tests...' . PHP_EOL); - $kernel->call('db:seed', ['--database' => 'testing']); + $kernel->call('db:seed'); } else { $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); } diff --git a/composer.json b/composer.json index 5c077a4bf6..30636ebd13 100644 --- a/composer.json +++ b/composer.json @@ -3,56 +3,67 @@ "description": "The free, open-source game management panel. Supporting Minecraft, Spigot, BungeeCord, and SRCDS servers.", "license": "MIT", "authors": [ + { + "name": "Matthew Penner", + "email": "matthew@pterodactyl.io", + "homepage": "https://github.com/matthewpi", + "role": "Lead Developer" + }, { "name": "Dane Everitt", "email": "dane@daneeveritt.com", "homepage": "https://github.com/DaneEveritt", - "role": "Lead Developer" + "role": "Developer" } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^8.2 || ^8.3", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", "ext-pdo_mysql": "*", + "ext-posix": "*", "ext-zip": "*", - "aws/aws-sdk-php": "^3.171", - "doctrine/dbal": "^2.12", - "fideloper/proxy": "^4.4", - "guzzlehttp/guzzle": "^7.2", - "hashids/hashids": "^4.1", - "laracasts/utilities": "^3.2", - "laravel/framework": "^8.22", - "laravel/helpers": "^1.4", - "laravel/tinker": "^2.5", - "laravel/ui": "^3.0", - "lcobucci/jwt": "^4.0", - "league/flysystem-aws-s3-v3": "^1.0", - "league/flysystem-memory": "^1.0", - "matriphe/iso-639": "^1.2", - "pragmarx/google2fa": "^5.0", - "predis/predis": "^1.1", - "prologue/alerts": "^0.4", - "psr/cache": "1.0.1", - "s1lentium/iptools": "^1.1", - "spatie/laravel-fractal": "^5.8", - "spatie/laravel-query-builder": "^3.3", - "staudenmeir/belongs-to-through": "^2.11", - "symfony/yaml": "^4.4", - "webmozart/assert": "^1.9" + "aws/aws-sdk-php": "^3.369", + "guzzlehttp/guzzle": "^7.9.2", + "hashids/hashids": "^5.0.2", + "laracasts/utilities": "~3.2.3", + "laravel/framework": "^11.31.0", + "laravel/helpers": "^1.8.0", + "laravel/sanctum": "^4.2.0", + "laravel/tinker": "^2.10.0", + "laravel/ui": "~4.5.2", + "lcobucci/jwt": "^5.6.0", + "league/flysystem-aws-s3-v3": "^3.30.1", + "league/flysystem-memory": "^3.29.0", + "matriphe/iso-639": "~2.0", + "phpseclib/phpseclib": "^3.0.47", + "pragmarx/google2fa": "~8.0.3", + "predis/predis": "^3.3.0", + "prologue/alerts": "^1.3.0", + "psr/cache": "^3.0.0", + "s1lentium/iptools": "~1.2.0", + "spatie/laravel-fractal": "^6.3.2", + "spatie/laravel-query-builder": "^6.3.6", + "staudenmeir/belongs-to-through": "^2.16.0", + "symfony/http-client": "^7.4.0", + "symfony/mailgun-mailer": "^7.4.0", + "symfony/postmark-mailer": "^7.4.0", + "symfony/yaml": "^7.4.0", + "webmozart/assert": "^1.11.0" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.5", - "barryvdh/laravel-ide-helper": "^2.9", - "facade/ignition": "^2.5", - "fakerphp/faker": "^1.13", - "friendsofphp/php-cs-fixer": "^2.18", - "laravel/dusk": "^6.11", - "mockery/mockery": "^1.4", - "nunomaduro/collision": "^5.2", - "php-mock/php-mock-phpunit": "^2.6", - "phpunit/phpunit": "^9.0" + "barryvdh/laravel-ide-helper": "^3.6", + "fakerphp/faker": "^1.24", + "friendsofphp/php-cs-fixer": "~3.64.0", + "itsgoingd/clockwork": "^5.3", + "larastan/larastan": "^3.8", + "mockery/mockery": "^1.6", + "nunomaduro/collision": "^8.5", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpunit/phpunit": "^10.5.62", + "spatie/laravel-ignition": "^2.9" }, "autoload": { "files": [ @@ -70,22 +81,29 @@ } }, "scripts": { - "php-cs-fixer": "php-cs-fixer fix --diff --diff-format=udiff --config=./.php_cs.dist", + "cs:fix": "php-cs-fixer fix", + "cs:check": "php-cs-fixer fix --dry-run --diff --verbose", + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi || true" + ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ - "@php artisan key:generate" - ], - "post-autoload-dump": [ - "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", - "@php artisan package:discover || true" + "@php artisan key:generate --ansi", + "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"", + "@php artisan migrate --graceful --ansi" ] }, - "prefer-stable": true, "config": { + "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true, - "optimize-autoloader": false - } + "platform": { + "php": "8.2.0" + } + }, + "minimum-stability": "stable", + "prefer-stable": true } diff --git a/composer.lock b/composer.lock index a45f81aa2f..1d5f431656 100644 --- a/composer.lock +++ b/composer.lock @@ -4,53 +4,111 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "87961b026d9057d13cfc3f9cb21d367d", + "content-hash": "4040741d3196c70ae44e03a7cdfca6e4", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, { "name": "aws/aws-sdk-php", - "version": "3.184.6", + "version": "3.369.34", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "0b7187c96ced465d400ad9427157e05ddee68edc" + "reference": "5db21cd0e5c8a5b5344596649033acf861a5e117" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0b7187c96ced465d400ad9427157e05ddee68edc", - "reference": "0b7187c96ced465d400ad9427157e05ddee68edc", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5db21cd0e5c8a5b5344596649033acf861a5e117", + "reference": "5db21cd0e5c8a5b5344596649033acf861a5e117", "shasum": "" }, "require": { + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", - "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.7.0", - "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^1.0 || ^2.0", + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", + "composer/composer": "^2.7.8", + "dms/phpunit-arraysubset-asserts": "^0.4.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", - "ext-pcntl": "*", "ext-sockets": "*", - "nette/neon": "^2.3", - "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35|^5.4.3", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" + "phpunit/phpunit": "^9.6", + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "yoast/phpunit-polyfills": "^2.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", "doctrine/cache": "To use the DoctrineCacheAdapter", "ext-curl": "To send requests using cURL", "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-pcntl": "To use client-side monitoring", "ext-sockets": "To use client-side monitoring" }, "type": "library", @@ -60,11 +118,14 @@ } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { "Aws\\": "src/" }, - "files": [ - "src/functions.php" + "exclude-from-classmap": [ + "src/data/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -90,34 +151,33 @@ "sdk" ], "support": { - "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.184.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.34" }, - "time": "2021-06-17T18:24:56+00:00" + "time": "2026-02-13T19:09:12+00:00" }, { "name": "brick/math", - "version": "0.9.2", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.1 || ^8.0" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.3.2" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -137,59 +197,55 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.2" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" + "url": "https://github.com/BenMorel", + "type": "github" } ], - "time": "2021-01-20T22:51:39+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { - "name": "doctrine/cache", - "version": "1.11.3", + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d" + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/3bb5588cec00a0268829cc4a518490df6741af9d", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { - "php": "~7.1 || ^8.0" + "php": "^8.1" }, "conflict": { - "doctrine/common": ">2.2,<2.4", - "psr/cache": ">=3" + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0", - "symfony/cache": "^4.4 || ^5.2" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" } }, "notification-url": "https://packagist.org/downloads/", @@ -198,242 +254,71 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" } ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", + "description": "Types to use Carbon in Doctrine", "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" + "carbon", + "date", + "datetime", + "doctrine", + "time" ], "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.11.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2021-05-25T09:01:55+00:00" - }, - { - "name": "doctrine/dbal", - "version": "2.13.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c800380457948e65bbd30ba92cc17cda108bf8c9", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.1 || ^8" - }, - "require-dev": { - "doctrine/coding-standard": "8.2.0", - "jetbrains/phpstorm-stubs": "2020.2", - "phpstan/phpstan": "0.12.81", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.0", - "squizlabs/php_codesniffer": "3.6.0", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.6.4" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlanywhere", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.1" + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" + "url": "https://github.com/kylekatarnls", + "type": "github" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://opencollective.com/Carbon", + "type": "open_collective" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", "type": "tidelift" } ], - "time": "2021-04-17T17:30:19+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { - "name": "doctrine/event-manager", - "version": "1.1.1", + "name": "dflydev/dot-access-data", + "version": "v3.0.3", "source": { "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "conflict": { - "doctrine/common": "<2.9@dev" - }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Dflydev\\DotAccessData\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -442,92 +327,68 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" }, { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" } ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" + "access", + "data", + "dot", + "notation" ], "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2020-05-29T18:28:51+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^7.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -572,7 +433,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.x" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -588,39 +449,36 @@ "type": "tidelift" } ], - "time": "2020-05-29T15:13:26+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -652,7 +510,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -668,36 +526,39 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.1.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.7.0" + "php": "^8.2|^8.3|^8.4|^8.5" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-webmozart-assert": "^0.12.7", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -721,7 +582,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" }, "funding": [ { @@ -729,31 +590,30 @@ "type": "github" } ], - "time": "2020-11-24T19:55:57+00:00" + "time": "2025-10-31T18:51:33+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.25", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { - "doctrine/lexer": "^1.0.1", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.10" + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "dominicsayers/isemail": "^3.0.7", - "phpunit/phpunit": "^4.8.36|^7.5.15", - "satooshi/php-coveralls": "^1.0.1" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -761,7 +621,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -789,7 +649,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -797,42 +657,40 @@ "type": "github" } ], - "time": "2020-12-29T14:50:06+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { - "name": "fideloper/proxy", - "version": "4.4.1", + "name": "fruitcake/php-cors", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0" + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/c073b2bd04d1c90e04dc1b787662b558dd65ade0", - "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", "shasum": "" }, "require": { - "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0", - "php": ">=5.4.0" + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" }, "require-dev": { - "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.0" + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" }, "type": "library", "extra": { - "laravel": { - "providers": [ - "Fideloper\\Proxy\\TrustedProxyServiceProvider" - ] + "branch-alias": { + "dev-master": "1.3-dev" } }, "autoload": { "psr-4": { - "Fideloper\\Proxy\\": "src/" + "Fruitcake\\Cors\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -841,49 +699,59 @@ ], "authors": [ { - "name": "Chris Fidao", - "email": "fideloper@gmail.com" + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" } ], - "description": "Set trusted proxies for Laravel", + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", "keywords": [ - "load balancing", - "proxy", - "trusted proxy" + "cors", + "laravel", + "symfony" ], "support": { - "issues": "https://github.com/fideloper/TrustedProxy/issues", - "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" }, - "time": "2020-10-22T13:48:01+00:00" + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.0.1", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { - "php": "^7.0|^8.0", - "phpoption/phpoption": "^1.7.3" + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { "GrahamCampbell\\ResultType\\": "src/" @@ -896,7 +764,8 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "An Implementation Of The Result Type", @@ -909,7 +778,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -921,38 +790,40 @@ "type": "tidelift" } ], - "time": "2020-04-13T13:17:36+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.3.0", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7008573787b430c1c1f650e3722d9bba59967628" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", - "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7 || ^2.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0" + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "provide": { "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1" + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { "ext-curl": "Required for CURL handler support", @@ -961,36 +832,61 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "7.3-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, { "name": "Márk Sági-Kazár", "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", @@ -1004,7 +900,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -1016,59 +912,69 @@ "type": "github" }, { - "url": "https://github.com/alexeyshockov", - "type": "github" - }, - { - "url": "https://github.com/gmponos", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" } ], - "time": "2021-03-23T11:33:13+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.4.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.4-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", @@ -1077,66 +983,107 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.1" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, - "time": "2021-03-07T09:25:29+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.2", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.7-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, { "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -1152,31 +1099,130 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.2" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" }, - "time": "2021-04-26T09:17:50+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:27:06+00:00" }, { "name": "hashids/hashids", - "version": "4.1.0", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/vinkla/hashids.git", - "reference": "8cab111f78e0bd9c76953b082919fc9e251761be" + "reference": "197171016b77ddf14e259e186559152eb3f8cf33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vinkla/hashids/zipball/8cab111f78e0bd9c76953b082919fc9e251761be", - "reference": "8cab111f78e0bd9c76953b082919fc9e251761be", + "url": "https://api.github.com/repos/vinkla/hashids/zipball/197171016b77ddf14e259e186559152eb3f8cf33", + "reference": "197171016b77ddf14e259e186559152eb3f8cf33", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^8.0 || ^9.4", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-bcmath": "Required to use BC Math arbitrary precision mathematics (*).", @@ -1185,7 +1231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1222,40 +1268,40 @@ ], "support": { "issues": "https://github.com/vinkla/hashids/issues", - "source": "https://github.com/vinkla/hashids/tree/4.1.0" + "source": "https://github.com/vinkla/hashids/tree/5.0.2" }, - "time": "2020-11-26T19:24:33+00:00" + "time": "2023-02-23T15:00:54+00:00" }, { "name": "laracasts/utilities", - "version": "3.2", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "26b1712166a366e3f8a1fd1452d0c3b76cad612a" + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/26b1712166a366e3f8a1fd1452d0c3b76cad612a", - "reference": "26b1712166a366e3f8a1fd1452d0c3b76cad612a", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", "shasum": "" }, "require": { - "illuminate/support": "^5.0|^6.0|^7.0|^8.0", - "php": ">=5.5.0|>=7.2.5" + "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": ">=5.5.0|>=7.2.5|>=8.0.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": ">=2.0" }, "type": "library", "extra": { "laravel": { - "providers": [ - "Laracasts\\Utilities\\JavaScript\\JavaScriptServiceProvider" - ], "aliases": { "JavaScript": "Laracasts\\Utilities\\JavaScript\\JavaScriptFacade" - } + }, + "providers": [ + "Laracasts\\Utilities\\JavaScript\\JavaScriptServiceProvider" + ] } }, "autoload": { @@ -1283,59 +1329,77 @@ ], "support": { "issues": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/issues", - "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2" + "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.4" }, - "time": "2020-09-07T13:29:37+00:00" + "time": "2025-02-27T15:16:31+00:00" }, { "name": "laravel/framework", - "version": "v8.47.0", + "version": "v11.47.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "93db226946453f4285558b7c3166ddb6e7ea5400" + "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/93db226946453f4285558b7c3166ddb6e7ea5400", - "reference": "93db226946453f4285558b7c3166ddb6e7ea5400", + "url": "https://api.github.com/repos/laravel/framework/zipball/86693ffa1ba32f56f8c44e31416c6665095a62c5", + "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5", "shasum": "" }, "require": { - "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^3.0.2", - "egulias/email-validator": "^2.1.10", - "ext-json": "*", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12|^0.13|^0.14", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", - "league/commonmark": "^1.3", - "league/flysystem": "^1.1", - "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.31", - "opis/closure": "^3.6", - "php": "^7.3|^8.0", - "psr/container": "^1.0", - "psr/simple-cache": "^1.0", - "ramsey/uuid": "^4.0", - "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^5.1.4", - "symfony/error-handler": "^5.1.4", - "symfony/finder": "^5.1.4", - "symfony/http-foundation": "^5.1.4", - "symfony/http-kernel": "^5.1.4", - "symfony/mime": "^5.1.4", - "symfony/process": "^5.1.4", - "symfony/routing": "^5.1.4", - "symfony/var-dumper": "^5.1.4", - "tijsverkoyen/css-to-inline-styles": "^2.2.2", - "vlucas/phpdotenv": "^5.2", - "voku/portable-ascii": "^1.4.8" + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.7", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^2.72.6|^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.0.3", + "symfony/error-handler": "^7.0.3", + "symfony/finder": "^7.0.3", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.0.3", + "symfony/mailer": "^7.0.3", + "symfony/mime": "^7.0.3", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.0.3", + "symfony/routing": "^7.0.3", + "symfony/uid": "^7.0.3", + "symfony/var-dumper": "^7.0.3", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" }, "conflict": { "tightenco/collect": "<5.5.33" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "replace": { "illuminate/auth": "self.version", @@ -1343,6 +1407,8 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", "illuminate/container": "self.version", @@ -1360,6 +1426,7 @@ "illuminate/notifications": "self.version", "illuminate/pagination": "self.version", "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", "illuminate/routing": "self.version", @@ -1368,68 +1435,94 @@ "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "illuminate/view": "self.version", + "spatie/once": "*" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", - "doctrine/dbal": "^2.6|^3.0", - "filp/whoops": "^2.8", - "guzzlehttp/guzzle": "^6.5.5|^7.0.1", - "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.4.2", - "orchestra/testbench-core": "^6.8", - "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.5.8|^9.3.3", - "predis/predis": "^1.1.2", - "symfony/cache": "^5.1.4" + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^9.16.1", + "pda/pheanstalk": "^5.0.6", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", + "predis/predis": "^2.3", + "resend/resend-php": "^0.10.0", + "symfony/cache": "^7.0.3", + "symfony/http-client": "^7.0.3", + "symfony/psr-http-message-bridge": "^7.0.3", + "symfony/translation": "^7.0.3" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", - "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", - "ext-pcntl": "Required to use all features of the queue worker.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", - "filp/whoops": "Required for friendly error pages in development (^2.8).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", - "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.4.2).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", - "predis/predis": "Required to use the predis connector (^1.1.2).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", - "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "8.x-dev" + "dev-master": "11.x-dev" } }, "autoload": { "files": [ + "src/Illuminate/Collections/functions.php", "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { "Illuminate\\": "src/Illuminate/", "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", - "src/Illuminate/Collections/" + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" ] } }, @@ -1453,28 +1546,29 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-06-15T14:00:32+00:00" + "time": "2025-11-28T18:20:11+00:00" }, { "name": "laravel/helpers", - "version": "v1.4.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac" + "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/febb10d8daaf86123825de2cb87f789a3371f0ac", - "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac", + "url": "https://api.github.com/repos/laravel/helpers/zipball/98499eea4c1cca76fb0fb37ed365a468773daf0a", + "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0", - "php": "^7.1.3|^8.0" + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.0|^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" }, "type": "library", "extra": { @@ -1498,7 +1592,7 @@ }, { "name": "Dries Vints", - "email": "dries.vints@gmail.com" + "email": "dries@laravel.com" } ], "description": "Provides backwards compatibility for helpers in the latest Laravel release.", @@ -1507,114 +1601,108 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.4.1" + "source": "https://github.com/laravel/helpers/tree/v1.8.2" }, - "time": "2021-02-16T15:27:11+00:00" + "time": "2025-11-25T14:46:28+00:00" }, { - "name": "laravel/tinker", - "version": "v2.6.1", + "name": "laravel/prompts", + "version": "v0.3.8", "source": { "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "04ad32c1a3328081097a181875733fa51f402083" + "url": "https://github.com/laravel/prompts.git", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", - "reference": "04ad32c1a3328081097a181875733fa51f402083", + "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/contracts": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", - "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4", - "symfony/var-dumper": "^4.3.4|^5.0" + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "mockery/mockery": "~1.3.3|^1.4.2", - "phpunit/phpunit": "^8.5.8|^9.3.3" + "illuminate/collections": "^10.0|^11.0|^12.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + "ext-pcntl": "Required for the spinner to be animated." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Tinker\\TinkerServiceProvider" - ] + "dev-main": "0.3.x-dev" } }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { - "Laravel\\Tinker\\": "src/" + "Laravel\\Prompts\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Powerful REPL for the Laravel framework.", - "keywords": [ - "REPL", - "Tinker", - "laravel", - "psysh" - ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { - "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.6.1" + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.8" }, - "time": "2021-03-02T16:53:12+00:00" + "time": "2025-11-21T20:52:52+00:00" }, { - "name": "laravel/ui", - "version": "v3.3.0", + "name": "laravel/sanctum", + "version": "v4.2.1", "source": { "type": "git", - "url": "https://github.com/laravel/ui.git", - "reference": "07d725813350c695c779382cbd6dac0ab8665537" + "url": "https://github.com/laravel/sanctum.git", + "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/07d725813350c695c779382cbd6dac0ab8665537", - "reference": "07d725813350c695c779382cbd6dac0ab8665537", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/f5fb373be39a246c74a060f2cf2ae2c2145b3664", + "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664", "shasum": "" }, "require": { - "illuminate/console": "^8.42", - "illuminate/filesystem": "^8.42", - "illuminate/support": "^8.42", - "illuminate/validation": "^8.42", - "php": "^7.3|^8.0" + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/contracts": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "php": "^8.2", + "symfony/console": "^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.15|^10.8", + "phpstan/phpstan": "^1.10" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - }, "laravel": { "providers": [ - "Laravel\\Ui\\UiServiceProvider" + "Laravel\\Sanctum\\SanctumServiceProvider" ] } }, "autoload": { "psr-4": { - "Laravel\\Ui\\": "src/", - "Illuminate\\Foundation\\Auth\\": "auth-backend/" + "Laravel\\Sanctum\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1627,48 +1715,51 @@ "email": "taylor@laravel.com" } ], - "description": "Laravel UI utilities and presets.", + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", "keywords": [ + "auth", "laravel", - "ui" + "sanctum" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.3.0" + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" }, - "time": "2021-05-25T16:45:33+00:00" + "time": "2025-11-21T13:59:03+00:00" }, { - "name": "lcobucci/clock", - "version": "2.0.0", + "name": "laravel/serializable-closure", + "version": "v2.0.7", "source": { "type": "git", - "url": "https://github.com/lcobucci/clock.git", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.1" }, "require-dev": { - "infection/infection": "^0.17", - "lcobucci/coding-standard": "^6.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-code-coverage": "9.1.4", - "phpunit/phpunit": "9.3.7" + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { - "Lcobucci\\Clock\\": "src" + "Laravel\\SerializableClosure\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1677,78 +1768,205 @@ ], "authors": [ { - "name": "Luís Cobucci", - "email": "lcobucci@gmail.com" - } - ], - "description": "Yet another clock abstraction", - "support": { - "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.0.x" - }, - "funding": [ - { - "url": "https://github.com/lcobucci", - "type": "github" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" }, { - "url": "https://www.patreon.com/lcobucci", - "type": "patreon" + "name": "Nuno Maduro", + "email": "nuno@laravel.com" } ], - "time": "2020-08-27T18:56:02+00:00" + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2025-11-21T20:52:36+00:00" }, { - "name": "lcobucci/jwt", - "version": "4.1.4", + "name": "laravel/tinker", + "version": "v2.10.2", "source": { "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "71cf170102c8371ccd933fa4df6252086d144de6" + "url": "https://github.com/laravel/tinker.git", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/71cf170102c8371ccd933fa4df6252086d144de6", - "reference": "71cf170102c8371ccd933fa4df6252086d144de6", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", "shasum": "" }, "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-openssl": "*", - "ext-sodium": "*", - "lcobucci/clock": "^2.0", - "php": "^7.4 || ^8.0" + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" }, "require-dev": { - "infection/infection": "^0.21", - "lcobucci/coding-standard": "^6.0", - "mikey179/vfsstream": "^1.6.7", - "phpbench/phpbench": "^1.0@alpha", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-invoker": "^3.1", - "phpunit/phpunit": "^9.5" + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, "autoload": { "psr-4": { - "Lcobucci\\JWT\\": "src" + "Laravel\\Tinker\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Luís Cobucci", - "email": "lcobucci@gmail.com", - "role": "Developer" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.10.2" + }, + "time": "2025-11-20T16:29:12+00:00" + }, + { + "name": "laravel/ui", + "version": "v4.5.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/ui.git", + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.21|^10.0|^11.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0", + "illuminate/support": "^9.21|^10.0|^11.0", + "illuminate/validation": "^9.21|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^7.35|^8.15|^9.0", + "phpunit/phpunit": "^9.3|^10.4|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "support": { + "source": "https://github.com/laravel/ui/tree/v4.5.2" + }, + "time": "2024-05-08T18:07:10+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" } ], "description": "A simple library to work with JSON Web Token and JSON Web Signature", @@ -1758,7 +1976,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.1.4" + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" }, "funding": [ { @@ -1770,46 +1988,59 @@ "type": "patreon" } ], - "time": "2021-03-23T23:53:08+00:00" + "time": "2025-10-17T11:30:53+00:00" }, { "name": "league/commonmark", - "version": "1.6.4", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2" + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c3c8b7217c52572fb42aaf84211abccf75a151b2", - "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "scrutinizer/ocular": "1.7.*" + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.29.2", - "erusev/parsedown": "~1.0", + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", "ext-json": "*", "github/gfm": "0.29.0", - "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", - "scrutinizer/ocular": "^1.5", - "symfony/finder": "^4.2" + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" }, - "bin": [ - "bin/commonmark" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, "autoload": { "psr-4": { "League\\CommonMark\\": "src" @@ -1827,7 +2058,7 @@ "role": "Lead Developer" } ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", "homepage": "https://commonmark.thephpleague.com", "keywords": [ "commonmark", @@ -1841,15 +2072,12 @@ ], "support": { "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", "issues": "https://github.com/thephpleague/commonmark/issues", "rss": "https://github.com/thephpleague/commonmark/releases.atom", "source": "https://github.com/thephpleague/commonmark" }, "funding": [ - { - "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", - "type": "custom" - }, { "url": "https://www.colinodell.com/sponsor", "type": "custom" @@ -1862,68 +2090,146 @@ "url": "https://github.com/colinodell", "type": "github" }, - { - "url": "https://www.patreon.com/colinodell", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/league/commonmark", "type": "tidelift" } ], - "time": "2021-06-19T20:08:14+00:00" + "time": "2025-11-26T21:48:24+00:00" }, { - "name": "league/flysystem", - "version": "1.1.3", + "name": "league/config", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.30.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "League\\Flysystem\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1933,73 +2239,108 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "File storage abstraction for PHP", "keywords": [ - "Cloud Files", "WebDAV", - "abstraction", "aws", "cloud", - "copy.com", - "dropbox", - "file systems", + "file", "files", "filesystem", "filesystems", "ftp", - "rackspace", - "remote", "s3", "sftp", "storage" ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.x" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" }, - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } - ], - "time": "2020-08-23T07:39:11+00:00" + "time": "2025-11-10T17:13:11+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.29", + "version": "3.30.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972" + "reference": "d286e896083bed3190574b8b088b557b59eb66f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d286e896083bed3190574b8b088b557b59eb66f5", + "reference": "d286e896083bed3190574b8b088b557b59eb66f5", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.20.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" + "aws/aws-sdk-php": "^3.295.10", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.30.1" + }, + "time": "2025-10-20T15:27:33+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.30.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "shasum": "" }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", "autoload": { "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" + "League\\Flysystem\\Local\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2009,45 +2350,45 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], "support": { - "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" }, - "time": "2020-10-08T18:58:37+00:00" + "time": "2025-11-10T11:23:37+00:00" }, { "name": "league/flysystem-memory", - "version": "1.0.2", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-memory.git", - "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757" + "reference": "219c79ad8b1d614a58ac17b775bfb3a6b7228126" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d0e87477c32e29f999b4de05e64c1adcddb51757", - "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/219c79ad8b1d614a58ac17b775bfb3a6b7228126", + "reference": "219c79ad8b1d614a58ac17b775bfb3a6b7228126", "shasum": "" }, "require": { - "league/flysystem": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.10" + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "php": "^8.0.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { - "League\\Flysystem\\Memory\\": "src/" + "League\\Flysystem\\InMemory\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2056,59 +2397,60 @@ ], "authors": [ { - "name": "Chris Leppanen", - "email": "chris.leppanen@gmail.com", - "role": "Developer" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "An in-memory adapter for Flysystem.", - "homepage": "https://github.com/thephpleague/flysystem-memory", + "description": "In-memory filesystem adapter for Flysystem.", "keywords": [ "Flysystem", - "adapter", + "file", + "files", + "filesystem", "memory" ], "support": { - "issues": "https://github.com/thephpleague/flysystem-memory/issues", - "source": "https://github.com/thephpleague/flysystem-memory/tree/1.0.2" + "source": "https://github.com/thephpleague/flysystem-memory/tree/3.29.0" }, - "time": "2019-05-30T21:34:13+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/fractal", - "version": "0.19.2", + "version": "0.20.2", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c" + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/06dc15f6ba38f2dde2f919d3095d13b571190a7c", - "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", "shasum": "" }, "require": { - "php": ">=5.4" + "php": ">=7.4" }, "require-dev": { "doctrine/orm": "^2.5", "illuminate/contracts": "~5.0", - "mockery/mockery": "~0.9", - "pagerfanta/pagerfanta": "~1.0.0", - "phpunit/phpunit": "^4.8.35 || ^7.5", - "squizlabs/php_codesniffer": "~1.5|~2.0|~3.4", - "zendframework/zend-paginator": "~2.3" + "laminas/laminas-paginator": "~2.12", + "mockery/mockery": "^1.3", + "pagerfanta/pagerfanta": "~1.0.0|~4.0.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "~3.4", + "vimeo/psalm": "^4.30" }, "suggest": { "illuminate/pagination": "The Illuminate Pagination component.", - "pagerfanta/pagerfanta": "Pagerfanta Paginator", - "zendframework/zend-paginator": "Zend Framework Paginator" + "laminas/laminas-paginator": "Laminas Framework Paginator", + "pagerfanta/pagerfanta": "Pagerfanta Paginator" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.13-dev" + "dev-master": "0.20.x-dev" } }, "autoload": { @@ -2138,32 +2480,32 @@ ], "support": { "issues": "https://github.com/thephpleague/fractal/issues", - "source": "https://github.com/thephpleague/fractal/tree/0.19.2" + "source": "https://github.com/thephpleague/fractal/tree/0.20.2" }, - "time": "2020-01-24T23:17:29+00:00" + "time": "2025-02-14T21:33:14+00:00" }, { "name": "league/mime-type-detection", - "version": "1.7.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.18", + "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -2184,7 +2526,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -2196,27 +2538,217 @@ "type": "tidelift" } ], - "time": "2021-01-18T20:58:21+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { - "name": "matriphe/iso-639", - "version": "1.2", + "name": "league/uri", + "version": "7.6.0", "source": { "type": "git", - "url": "https://github.com/matriphe/php-iso-639.git", - "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1" + "url": "https://github.com/thephpleague/uri.git", + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matriphe/php-iso-639/zipball/0245d844daeefdd22a54b47103ffdb0e03c323e1", - "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, - "require-dev": { - "phpunit/phpunit": "^4.7" + "require": { + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, - "type": "library", - "autoload": { + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.6.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-11-18T12:17:23+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.6.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-11-18T12:17:23+00:00" + }, + { + "name": "matriphe/iso-639", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/matriphe/php-iso-639.git", + "reference": "aa2ddce7eb9000570e6398bf52f114ebf9f0691a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matriphe/php-iso-639/zipball/aa2ddce7eb9000570e6398bf52f114ebf9f0691a", + "reference": "aa2ddce7eb9000570e6398bf52f114ebf9f0691a", + "shasum": "" + }, + "require": { + "php": "8.*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.75", + "phpunit/phpunit": ">=10" + }, + "suggest": { + "ext-mbstring": "For better multibyte character support in text transformations", + "ext-xdebug": "For code coverage when running tests" + }, + "type": "library", + "autoload": { "psr-4": { "Matriphe\\ISO639\\": "src/" } @@ -2242,65 +2774,72 @@ ], "support": { "issues": "https://github.com/matriphe/php-iso-639/issues", - "source": "https://github.com/matriphe/php-iso-639/tree/master" + "source": "https://github.com/matriphe/php-iso-639/tree/v2.0.10" }, - "time": "2017-07-19T15:11:19+00:00" + "time": "2025-10-05T21:40:44+00:00" }, { "name": "monolog/monolog", - "version": "2.2.0", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", - "graylog2/gelf-php": "^1.4.2", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "^8.5", - "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", "ext-mbstring": "Allow to work properly with unicode symbols", "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -2328,7 +2867,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -2340,29 +2879,29 @@ "type": "tidelift" } ], - "time": "2020-12-14T13:15:25+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -2370,22 +2909,27 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.8-dev" } }, "autoload": { - "psr-4": { - "JmesPath\\": "src/" - }, "files": [ "src/JmesPath.php" - ] + ], + "psr-4": { + "JmesPath\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -2399,49 +2943,52 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2024-09-04T18:46:31+00:00" }, { "name": "nesbot/carbon", - "version": "2.49.0", + "version": "3.11.0", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "93d9db91c0235c486875d22f1e08b50bdf3e6eee" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/93d9db91c0235c486875d22f1e08b50bdf3e6eee", - "reference": "93d9db91c0235c486875d22f1e08b50bdf3e6eee", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1", "shasum": "" }, "require": { + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", - "php": "^7.1.8 || ^8.0", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^3.4 || ^4.0 || ^5.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/orm": "^2.7", - "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^2.0", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54", - "phpunit/phpunit": "^7.5.20 || ^8.5.14", - "squizlabs/php_codesniffer": "^3.4" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" }, "bin": [ "bin/carbon" ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.x-dev", - "dev-3.x": "3.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -2451,6 +2998,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -2466,57 +3017,218 @@ { "name": "Brian Nesbitt", "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" + "homepage": "https://markido.com" }, { "name": "kylekatarnls", - "homepage": "http://github.com/kylekatarnls" + "homepage": "https://github.com/kylekatarnls" } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "http://carbon.nesbot.com", + "homepage": "https://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], "support": { - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { - "url": "https://opencollective.com/Carbon", - "type": "open_collective" + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", "type": "tidelift" } ], - "time": "2021-06-02T07:31:40+00:00" + "time": "2025-12-02T21:04:28+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.3" + }, + "time": "2025-10-30T22:57:59+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.0" + }, + "time": "2025-12-01T17:49:23+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.5", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -2524,7 +3236,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -2548,44 +3260,57 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2021-05-03T19:11:20+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { - "name": "opis/closure", - "version": "3.6.2", + "name": "nunomaduro/termwind", + "version": "v2.3.3", "source": { "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0" + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.3.6" }, "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, "branch-alias": { - "dev-master": "3.6.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { - "psr-4": { - "Opis\\Closure\\": "src/" - }, "files": [ - "functions.php" - ] + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2593,50 +3318,61 @@ ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", + "description": "Its like Tailwind CSS, but for the console.", "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" + "cli", + "console", + "css", + "package", + "php", + "style" ], "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.2" + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" }, - "time": "2021-04-09T13:42:10+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2025-11-20T02:34:59+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.4.0", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", - "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" }, "type": "library", "autoload": { @@ -2682,7 +3418,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2020-12-06T15:14:20+00:00" + "time": "2025-09-24T15:06:41+00:00" }, { "name": "paragonie/random_compat", @@ -2736,29 +3472,33 @@ }, { "name": "phpoption/phpoption", - "version": "1.7.5", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -2773,11 +3513,13 @@ "authors": [ { "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "Option Type for PHP", @@ -2789,7 +3531,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -2801,42 +3543,144 @@ "type": "tidelift" } ], - "time": "2020-07-20T17:29:33+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { - "name": "pragmarx/google2fa", - "version": "v5.0.0", + "name": "phpseclib/phpseclib", + "version": "3.0.47", "source": { "type": "git", - "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/17c969c82f427dd916afe4be50bafc6299aef1b4", - "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "~1.0|~2.0", - "paragonie/random_compat": ">=1", - "php": ">=5.4", - "symfony/polyfill-php56": "~1.2" + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" }, "require-dev": { - "phpunit/phpunit": "~4|~5|~6" + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." }, "type": "library", - "extra": { - "component": "package", - "branch-alias": { - "dev-master": "2.0-dev" + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" } + ], + "time": "2025-10-06T01:07:24+00:00" + }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" }, + "type": "library", "autoload": { "psr-4": { - "PragmaRX\\Google2FA\\": "src/", - "PragmaRX\\Google2FA\\Tests\\": "tests/" + "PragmaRX\\Google2FA\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2859,33 +3703,36 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/master" + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" }, - "time": "2019-03-19T22:44:16+00:00" + "time": "2024-09-05T11:56:40+00:00" }, { "name": "predis/predis", - "version": "v1.1.7", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8" + "reference": "153097374b39a2f737fe700ebcd725642526cdec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/b240daa106d4e02f0c5b7079b41e31ddf66fddf8", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8", + "url": "https://api.github.com/repos/predis/predis/zipball/153097374b39a2f737fe700ebcd725642526cdec", + "reference": "153097374b39a2f737fe700ebcd725642526cdec", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0|^2.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpcov": "^6.0 || ^8.0", + "phpunit/phpunit": "^8.0 || ~9.4.4" }, "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis", - "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" }, "type": "library", "autoload": { @@ -2898,19 +3745,13 @@ "MIT" ], "authors": [ - { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net", - "role": "Creator & Maintainer" - }, { "name": "Till Krüss", "homepage": "https://till.im", "role": "Maintainer" } ], - "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "description": "A flexible and feature-complete Redis/Valkey client for PHP.", "homepage": "http://github.com/predis/predis", "keywords": [ "nosql", @@ -2919,7 +3760,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v1.1.7" + "source": "https://github.com/predis/predis/tree/v3.3.0" }, "funding": [ { @@ -2927,41 +3768,40 @@ "type": "github" } ], - "time": "2021-04-04T19:34:46+00:00" + "time": "2025-11-24T17:48:50+00:00" }, { "name": "prologue/alerts", - "version": "0.4.8", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/prologuephp/alerts.git", - "reference": "a6412e318c0171526bc8b25ef597f2cc61f5b800" + "reference": "0a73c65c70d4838a79cc927e8c966a00b2cefcfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/a6412e318c0171526bc8b25ef597f2cc61f5b800", - "reference": "a6412e318c0171526bc8b25ef597f2cc61f5b800", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/0a73c65c70d4838a79cc927e8c966a00b2cefcfc", + "reference": "0a73c65c70d4838a79cc927e8c966a00b2cefcfc", "shasum": "" }, "require": { - "illuminate/config": "~5|~6|~7|~8", - "illuminate/session": "~5|~6|~7|~8", - "illuminate/support": "~5|~6|~7|~8", - "php": ">=5.4.0" + "illuminate/config": "~9|^10|^11.0|^12.0", + "illuminate/session": "~9|^10|^11.0|^12.0", + "illuminate/support": "~9|^10|^11.0|^12.0" }, "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.1" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^9|^10.5" }, "type": "library", "extra": { "laravel": { - "providers": [ - "Prologue\\Alerts\\AlertsServiceProvider" - ], "aliases": { "Alert": "Prologue\\Alerts\\Facades\\Alert" - } + }, + "providers": [ + "Prologue\\Alerts\\AlertsServiceProvider" + ] } }, "autoload": { @@ -2995,26 +3835,26 @@ ], "support": { "issues": "https://github.com/prologuephp/alerts/issues", - "source": "https://github.com/prologuephp/alerts/tree/master" + "source": "https://github.com/prologuephp/alerts/tree/1.3.0" }, - "time": "2020-09-08T14:24:39+00:00" + "time": "2025-02-24T15:15:54+00:00" }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -3034,7 +3874,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -3044,28 +3884,81 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2022-11-25T14:36:26+00:00" }, { "name": "psr/container", - "version": "1.1.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -3092,9 +3985,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -3148,21 +4041,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -3182,7 +4075,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -3194,31 +4087,86 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -3233,7 +4181,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -3247,36 +4195,36 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3297,31 +4245,31 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -3336,7 +4284,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -3348,50 +4296,55 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2017-10-23T01:57:42+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "psy/psysh", - "version": "v0.10.8", + "version": "v0.12.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", - "php": "^8.0 || ^7.0 || ^5.5.9", - "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", - "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.*" + "composer/class-map-generator": "^1.6" }, "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", - "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, "bin": [ "bin/psysh" ], "type": "library", "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, "branch-alias": { - "dev-main": "0.10.x-dev" + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -3409,12 +4362,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -3423,9 +4375,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.10.8" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" }, - "time": "2021-04-10T16:23:39+00:00" + "time": "2026-02-11T15:05:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -3473,40 +4425,49 @@ }, { "name": "ramsey/collection", - "version": "1.1.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "php": "^8.1" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, "autoload": { "psr-4": { "Ramsey\\Collection\\": "src/" @@ -3523,7 +4484,7 @@ "homepage": "https://benramsey.com" } ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ "array", "collection", @@ -3534,69 +4495,53 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.1.3" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2021-01-21T17:40:04+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", - "version": "4.1.1", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "cd4032040a750077205918c86049aa0f43d22947" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", - "reference": "cd4032040a750077205918c86049aa0f43d22947", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8 || ^0.9", - "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", - "doctrine/annotations": "^1.8", - "goaop/framework": "^2", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^0.17.1", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", @@ -3604,24 +4549,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.x-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", "keywords": [ "guid", "identifier", @@ -3629,38 +4573,31 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - } - ], - "time": "2020-08-18T17:17:46+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "s1lentium/iptools", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/S1lentium/IPTools.git", - "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f" + "reference": "88be1aaaab3c50fc131ebe778e246215ff006d8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/f6f8ab6132ca7443bd7cced1681f5066d725fd5f", - "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f", + "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/88be1aaaab3c50fc131ebe778e246215ff006d8e", + "reference": "88be1aaaab3c50fc131ebe778e246215ff006d8e", "shasum": "" }, "require": { "ext-bcmath": "*", - "php": ">=5.4.0" + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "~1.0" + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9.0" }, "type": "library", "autoload": { @@ -3691,30 +4628,31 @@ ], "support": { "issues": "https://github.com/S1lentium/IPTools/issues", - "source": "https://github.com/S1lentium/IPTools/tree/master" + "source": "https://github.com/S1lentium/IPTools/tree/v1.2.0" }, - "time": "2018-09-19T06:15:53+00:00" + "time": "2022-08-17T14:28:59+00:00" }, { "name": "spatie/fractalistic", - "version": "2.9.1", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/spatie/fractalistic.git", - "reference": "c4fc2b223a8f7321ece449abc2c59cc5baf470fd" + "reference": "046c535f30b31a9356fc034ce75e8ee74614ed4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/c4fc2b223a8f7321ece449abc2c59cc5baf470fd", - "reference": "c4fc2b223a8f7321ece449abc2c59cc5baf470fd", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/046c535f30b31a9356fc034ce75e8ee74614ed4f", + "reference": "046c535f30b31a9356fc034ce75e8ee74614ed4f", "shasum": "" }, "require": { - "league/fractal": "^0.19.0", - "php": "^7.3|^8.0" + "league/fractal": "^0.20.1", + "php": "^7.4|^8.0" }, "require-dev": { - "illuminate/pagination": "~5.3.0|~5.4.0", + "illuminate/pagination": "~5.3.0|~5.4.0|^9.0", + "pestphp/pest": "^1.22", "phpunit/phpunit": "^9.0" }, "type": "library", @@ -3746,7 +4684,7 @@ ], "support": { "issues": "https://github.com/spatie/fractalistic/issues", - "source": "https://github.com/spatie/fractalistic/tree/2.9.1" + "source": "https://github.com/spatie/fractalistic/tree/2.11.0" }, "funding": [ { @@ -3754,50 +4692,54 @@ "type": "github" } ], - "time": "2020-11-12T18:19:10+00:00" + "time": "2025-01-27T09:52:33+00:00" }, { "name": "spatie/laravel-fractal", - "version": "5.8.1", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "be3ccd54e26742cd05b3637fb732fd9addfa28df" + "reference": "d078aa670233100e1309a0a7096c42f5b605ef29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/be3ccd54e26742cd05b3637fb732fd9addfa28df", - "reference": "be3ccd54e26742cd05b3637fb732fd9addfa28df", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/d078aa670233100e1309a0a7096c42f5b605ef29", + "reference": "d078aa670233100e1309a0a7096c42f5b605ef29", "shasum": "" }, "require": { - "illuminate/contracts": "^7.0|^8.0", - "illuminate/support": "^7.0|^8.0", - "php": "^7.2|^8.0", - "spatie/fractalistic": "^2.5" + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", + "league/fractal": "^0.20.1|^0.20", + "nesbot/carbon": "^2.63|^3.0", + "php": "^8.0", + "spatie/fractalistic": "^2.9.5|^2.9", + "spatie/laravel-package-tools": "^1.11" }, "require-dev": { "ext-json": "*", - "orchestra/testbench": "^5.0|^6.0" + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "pestphp/pest": "^1.22|^2.34|^3.0" }, "type": "library", "extra": { "laravel": { + "aliases": { + "Fractal": "Spatie\\Fractal\\Facades\\Fractal" + }, "providers": [ "Spatie\\Fractal\\FractalServiceProvider" - ], - "aliases": { - "Fractal": "Spatie\\Fractal\\FractalFacade" - } + ] } }, "autoload": { - "psr-4": { - "Spatie\\Fractal\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Spatie\\Fractal\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3823,8 +4765,7 @@ "transform" ], "support": { - "issues": "https://github.com/spatie/laravel-fractal/issues", - "source": "https://github.com/spatie/laravel-fractal/tree/5.8.1" + "source": "https://github.com/spatie/laravel-fractal/tree/6.3.2" }, "funding": [ { @@ -3832,46 +4773,38 @@ "type": "custom" } ], - "time": "2020-11-12T18:46:53+00:00" + "time": "2025-02-14T10:43:50+00:00" }, { - "name": "spatie/laravel-query-builder", - "version": "3.4.1", + "name": "spatie/laravel-package-tools", + "version": "1.92.7", "source": { "type": "git", - "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9" + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9", - "reference": "6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", "shasum": "" }, "require": { - "illuminate/database": "^6.20.13|^7.30.4|^8.22.2", - "illuminate/http": "^6.20.13|7.30.4|^8.22.2", - "illuminate/support": "^6.20.13|7.30.4|^8.22.2", - "php": "^7.3|^8.0" + "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", + "php": "^8.0" }, "require-dev": { - "ext-json": "*", - "laravel/legacy-factories": "^1.0.4", - "mockery/mockery": "^1.4", - "orchestra/testbench": "^4.9|^5.8|^6.3", - "phpunit/phpunit": "^9.0" + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", + "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.5.24|^10.5|^11.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" }, "type": "library", - "extra": { - "laravel": { - "providers": [ - "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" - ] - } - }, "autoload": { "psr-4": { - "Spatie\\QueryBuilder\\": "src" + "Spatie\\LaravelPackageTools\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3880,55 +4813,141 @@ ], "authors": [ { - "name": "Alex Vanderbist", - "email": "alex@spatie.be", - "homepage": "https://spatie.be", + "name": "Freek Van der Herten", + "email": "freek@spatie.be", "role": "Developer" } ], - "description": "Easily build Eloquent queries from API requests", - "homepage": "https://github.com/spatie/laravel-query-builder", + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", "keywords": [ - "laravel-query-builder", + "laravel-package-tools", "spatie" ], "support": { - "issues": "https://github.com/spatie/laravel-query-builder/issues", - "source": "https://github.com/spatie/laravel-query-builder" + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" }, "funding": [ { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" + "url": "https://github.com/spatie", + "type": "github" } ], - "time": "2021-05-24T10:13:32+00:00" + "time": "2025-07-17T15:46:43+00:00" }, { - "name": "staudenmeir/belongs-to-through", - "version": "v2.11.1", + "name": "spatie/laravel-query-builder", + "version": "6.3.6", "source": { "type": "git", - "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "d300afa1045e6541b79af2291336312613b5cd02" + "url": "https://github.com/spatie/laravel-query-builder.git", + "reference": "b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481", + "reference": "b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0|^11.0|^12.0", + "illuminate/http": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.11" + }, + "require-dev": { + "ext-json": "*", + "larastan/larastan": "^2.7 || ^3.3", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^7.0|^8.0|^10.0", + "pestphp/pest": "^2.0|^3.7|^4.0", + "phpunit/phpunit": "^10.0|^11.5.3|^12.0", + "spatie/invade": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\QueryBuilder\\": "src", + "Spatie\\QueryBuilder\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily build Eloquent queries from API requests", + "homepage": "https://github.com/spatie/laravel-query-builder", + "keywords": [ + "laravel-query-builder", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-query-builder/issues", + "source": "https://github.com/spatie/laravel-query-builder" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2025-10-20T09:50:21+00:00" + }, + { + "name": "staudenmeir/belongs-to-through", + "version": "v2.16.4", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/belongs-to-through.git", + "reference": "451496e4272c11d87b404791acdd352c606f29c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/d300afa1045e6541b79af2291336312613b5cd02", - "reference": "d300afa1045e6541b79af2291336312613b5cd02", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/451496e4272c11d87b404791acdd352c606f29c0", + "reference": "451496e4272c11d87b404791acdd352c606f29c0", "shasum": "" }, "require": { - "illuminate/database": "^8.0", - "php": "^7.3|^8.0" + "illuminate/database": "^11.0", + "php": "^8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "barryvdh/laravel-ide-helper": "^3.0", + "larastan/larastan": "^3.0", + "laravel/framework": "^11.0", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^9.5", + "phpunit/phpunit": "^11.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\BelongsToThrough\\IdeHelperServiceProvider" + ] + } + }, "autoload": { "psr-4": { - "Znck\\Eloquent\\": "src/" + "Znck\\Eloquent\\": "src/", + "Staudenmeir\\BelongsToThrough\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3948,7 +4967,7 @@ "description": "Laravel Eloquent BelongsToThrough relationships", "support": { "issues": "https://github.com/staudenmeir/belongs-to-through/issues", - "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.11.1" + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16.4" }, "funding": [ { @@ -3956,45 +4975,40 @@ "type": "custom" } ], - "time": "2020-11-16T19:39:09+00:00" + "time": "2025-02-20T19:20:43+00:00" }, { - "name": "swiftmailer/swiftmailer", - "version": "v6.2.7", + "name": "symfony/clock", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933" + "url": "https://github.com/symfony/clock.git", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", "shasum": "" }, "require": { - "egulias/email-validator": "^2.0|^3.1", - "php": ">=7.0.0", - "symfony/polyfill-iconv": "^1.0", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.0" + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" }, - "suggest": { - "ext-intl": "Needed to support internationalized email addresses" + "provide": { + "psr/clock-implementation": "1.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.2-dev" - } - }, "autoload": { "files": [ - "lib/swift_required.php" + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4003,83 +5017,87 @@ ], "authors": [ { - "name": "Chris Corbyn" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", "keywords": [ - "email", - "mail", - "mailer" + "clock", + "psr20", + "time" ], "support": { - "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7" + "source": "https://github.com/symfony/clock/tree/v7.4.0" }, "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, { "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-03-09T12:30:35+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/console", - "version": "v5.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4108,12 +5126,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.2" + "source": "https://github.com/symfony/console/tree/v7.4.0" }, "funding": [ { @@ -4124,29 +5142,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-12T09:42:48+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/css-selector", - "version": "v5.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -4178,7 +5200,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -4189,38 +5211,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:40:38+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -4245,7 +5271,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -4261,33 +5287,42 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/error-handler", - "version": "v5.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0e6768b8c0dcef26df087df2bbbaa143867a59b2" + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0e6768b8c0dcef26df087df2bbbaa143867a59b2", - "reference": "0e6768b8c0dcef26df087df2bbbaa143867a59b2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/var-dumper": "^4.4|^5.0" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { @@ -4314,7 +5349,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.0" }, "funding": [ { @@ -4325,53 +5360,53 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4399,7 +5434,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" }, "funding": [ { @@ -4410,42 +5445,43 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-10-28T09:38:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -4478,7 +5514,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -4494,29 +5530,34 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/finder", - "version": "v5.3.0", + "name": "symfony/filesystem", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" + "url": "https://github.com/symfony/filesystem.git", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4536,10 +5577,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -4550,47 +5591,45 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T12:52:38+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v2.4.0", + "name": "symfony/finder", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" + "url": "https://github.com/symfony/finder.git", + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.2" }, - "suggest": { - "symfony/http-client-implementation": "" + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - } + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4598,26 +5637,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to HTTP clients", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.0" }, "funding": [ { @@ -4628,46 +5659,71 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-04-11T23:07:08+00:00" + "time": "2025-11-05T05:42:40+00:00" }, { - "name": "symfony/http-foundation", - "version": "v5.3.2", + "name": "symfony/http-client", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "7b6dd714d95106b831aaa7f3c9c612ab886516bd" + "url": "https://github.com/symfony/http-client.git", + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7b6dd714d95106b831aaa7f3c9c612ab886516bd", - "reference": "7b6dd714d95106b831aaa7f3c9c612ab886516bd", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ee5e0e0139ab506f6063a230e631bed677c650a4", + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4679,18 +5735,21 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.3.2" + "source": "https://github.com/symfony/http-client/tree/v7.4.0" }, "funding": [ { @@ -4701,88 +5760,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-12T10:15:17+00:00" + "time": "2025-11-20T12:32:50+00:00" }, { - "name": "symfony/http-kernel", - "version": "v5.3.2", + "name": "symfony/http-client-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "e7021165d9dbfb4051296b8de827e92c8a7b5c87" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e7021165d9dbfb4051296b8de827e92c8a7b5c87", - "reference": "e7021165d9dbfb4051296b8de827e92c8a7b5c87", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "~1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4|^5.0", - "symfony/event-dispatcher": "^5.0", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^5.3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "symfony/browser-kit": "<4.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", - "twig/twig": "<2.13" - }, - "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.3", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" + "php": ">=8.1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" + "Symfony\\Contracts\\HttpClient\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4791,18 +5812,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a structured process for converting a Request into a Response", + "description": "Generic abstractions related to HTTP clients", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.3.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -4818,47 +5847,46 @@ "type": "tidelift" } ], - "time": "2021-06-17T14:18:27+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { - "name": "symfony/mime", - "version": "v5.3.2", + "name": "symfony/http-foundation", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "769c1720b68e964b13b58529c17d4a385c62167b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/769c1720b68e964b13b58529c17d4a385c62167b", + "reference": "769c1720b68e964b13b58529c17d4a385c62167b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" }, "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4" + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.1", - "symfony/property-info": "^4.4|^5.1", - "symfony/serializer": "^5.2" + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Mime\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4878,14 +5906,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.0" }, "funding": [ { @@ -4897,48 +5921,94 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-09T10:58:01+00:00" + "time": "2025-11-13T08:49:24+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "name": "symfony/http-kernel", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "7348193cd384495a755554382e4526f27c456085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7348193cd384495a755554382e4526f27c456085", + "reference": "7348193cd384495a755554382e4526f27c456085", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, - "suggest": { - "ext-ctype": "For best performance" + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Component\\HttpKernel\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4947,24 +6017,18 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.0" }, "funding": [ { @@ -4975,49 +6039,60 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2025-11-27T13:38:24+00:00" }, { - "name": "symfony/polyfill-iconv", - "version": "v1.23.0", + "name": "symfony/mailer", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" + "url": "https://github.com/symfony/mailer.git", + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", "shasum": "" }, "require": { - "php": ">=7.1" + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" }, - "suggest": { - "ext-iconv": "For best performance" + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" + "Symfony\\Component\\Mailer\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5026,25 +6101,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Iconv extension", + "description": "Helps sending emails", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.0" }, "funding": [ { @@ -5055,49 +6123,49 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2025-11-21T15:26:00+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.0", + "name": "symfony/mailgun-mailer", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + "url": "https://github.com/symfony/mailgun-mailer.git", + "reference": "ffbcdbf93ed0700f083a6307acfb8f78dd3f091b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/ffbcdbf93ed0700f083a6307acfb8f78dd3f091b", + "reference": "ffbcdbf93ed0700f083a6307acfb8f78dd3f091b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.2", + "symfony/mailer": "^7.2|^8.0" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "symfony/http-foundation": "<6.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "require-dev": { + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/webhook": "^6.4|^7.0|^8.0" }, + "type": "symfony-mailer-bridge", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + "Symfony\\Component\\Mailer\\Bridge\\Mailgun\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5106,26 +6174,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + "source": "https://github.com/symfony/mailgun-mailer/tree/v7.4.0" }, "funding": [ { @@ -5136,51 +6196,61 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.23.0", + "name": "symfony/mime", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" + "url": "https://github.com/symfony/mime.git", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", + "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" + "Symfony\\Component\\Mime\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5189,30 +6259,22 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" + "mime", + "mime-type" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0" + "source": "https://github.com/symfony/mime/tree/v7.4.0" }, "funding": [ { @@ -5223,53 +6285,54 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { - "ext-intl": "For best performance" + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], - "classmap": [ - "Resources/stubs" - ] + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5277,26 +6340,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", + "ctype", "polyfill", - "portable", - "shim" + "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -5307,50 +6368,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.23.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5366,17 +6428,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "grapheme", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -5387,38 +6450,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "symfony/polyfill-php56", - "version": "v1.20.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675", - "reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, - "type": "metapackage", + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -5427,24 +6503,30 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "idn", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php56/tree/v1.20.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -5455,46 +6537,53 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T14:02:19+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { - "name": "symfony/polyfill-php72", - "version": "v1.23.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5511,16 +6600,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -5531,50 +6622,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], - "classmap": [ - "Resources/stubs" - ] + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5590,16 +6686,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -5610,47 +6707,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.23.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -5682,7 +6780,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -5693,38 +6791,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "symfony/process", - "version": "v5.3.2", + "name": "symfony/polyfill-php83", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5733,18 +6843,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/process/tree/v5.3.2" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -5755,60 +6871,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-12T10:15:01+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/routing", - "version": "v5.3.0", + "name": "symfony/polyfill-php85", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/368e81376a8e049c37cb80ae87dbfbf411279199", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "^1.12", - "psr/log": "~1.0", - "symfony/config": "^5.3", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Polyfill\\Php85\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5817,24 +6923,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "router", - "routing", - "uri", - "url" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.3.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" }, "funding": [ { @@ -5845,47 +6951,53 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-06-23T16:12:55+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.4.0", + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1" + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" }, "suggest": { - "symfony/service-implementation": "" + "ext-uuid": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Polyfill\\Uuid\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -5894,26 +7006,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Symfony polyfill for uuid functions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "uuid" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -5924,49 +7034,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/string", - "version": "v5.3.2", + "name": "symfony/postmark-mailer", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + "url": "https://github.com/symfony/postmark-mailer.git", + "reference": "67eab9e06ff2adf74152df2ac95a07cef48eb7c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/67eab9e06ff2adf74152df2ac95a07cef48eb7c5", + "reference": "67eab9e06ff2adf74152df2ac95a07cef48eb7c5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "symfony/mailer": "^7.2|^8.0" + }, + "conflict": { + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/webhook": "^6.4|^7.0|^8.0" }, - "type": "library", + "type": "symfony-mailer-bridge", "autoload": { "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Component\\Mailer\\Bridge\\Postmark\\": "" }, - "files": [ - "Resources/functions.php" - ], "exclude-from-classmap": [ "/Tests/" ] @@ -5977,26 +7086,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "description": "Symfony Postmark Mailer Bridge", "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.2" + "source": "https://github.com/symfony/postmark-mailer/tree/v7.4.0" }, "funding": [ { @@ -6007,68 +7108,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { - "name": "symfony/translation", - "version": "v5.3.2", + "name": "symfony/process", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "7e2603bcc598e14804c4d2359d8dc4ee3c40391b" + "url": "https://github.com/symfony/process.git", + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7e2603bcc598e14804c4d2359d8dc4ee3c40391b", - "reference": "7e2603bcc598e14804c4d2359d8dc4ee3c40391b", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/translation-contracts": "^2.3" - }, - "conflict": { - "symfony/config": "<4.4", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" - }, - "provide": { - "symfony/translation-implementation": "2.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/polyfill-intl-icu": "^1.21", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "php": ">=8.2" }, "type": "library", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\Translation\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6088,10 +7159,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to internationalize your application", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.3.2" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -6102,47 +7173,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v2.4.0", + "name": "symfony/routing", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + "url": "https://github.com/symfony/routing.git", + "reference": "4720254cb2644a0b876233d258a32bf017330db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", + "reference": "4720254cb2644a0b876233d258a32bf017330db7", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "symfony/translation-implementation": "" + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6150,26 +7230,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "router", + "routing", + "uri", + "url" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/routing/tree/v7.4.0" }, "funding": [ { @@ -6180,60 +7258,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { - "name": "symfony/var-dumper", - "version": "v5.3.2", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "905a22c68b292ffb6f20d7636c36b220d1fba5ae" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/905a22c68b292ffb6f20d7636c36b220d1fba5ae", - "reference": "905a22c68b292ffb6f20d7636c36b220d1fba5ae", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + "ext-psr": "<1.1|>=2" }, - "bin": [ - "Resources/bin/var-dump-server" - ], "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { - "files": [ - "Resources/functions/dump.php" - ], "psr-4": { - "Symfony\\Component\\VarDumper\\": "" + "Symfony\\Contracts\\Service\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6250,14 +7323,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "debug", - "dump" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.3.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -6268,44 +7345,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { - "name": "symfony/yaml", - "version": "v4.4.25", + "name": "symfony/string", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc" + "url": "https://github.com/symfony/string.git", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc", - "reference": "81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<3.4" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "Symfony\\Component\\Yaml\\": "" + "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6317,18 +7406,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Loads and dumps YAML files", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.25" + "source": "https://github.com/symfony/string/tree/v7.4.0" }, "funding": [ { @@ -6339,174 +7436,228 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:39:37+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { - "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.3", + "name": "symfony/translation", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5" + "url": "https://github.com/symfony/translation.git", + "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5", + "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5" + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "TijsVerkoyen\\CssToInlineStyles\\": "src" - } + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Tijs Verkoyen", - "email": "css_to_inline_styles@verkoyen.eu", - "role": "Developer" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", - "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + "source": "https://github.com/symfony/translation/tree/v7.4.0" }, - "time": "2020-07-13T06:12:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" }, { - "name": "vlucas/phpdotenv", - "version": "v5.3.0", + "name": "symfony/translation-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { - "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.1", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.7.4", - "symfony/polyfill-ctype": "^1.17", - "symfony/polyfill-mbstring": "^1.17", - "symfony/polyfill-php80": "^1.17" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" - }, - "suggest": { - "ext-filter": "Required to use the boolean validator." + "php": ">=8.1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "5.3-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "Dotenv\\": "src/" - } + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "homepage": "https://gjcampbell.co.uk/" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Vance Lucas", - "email": "vance@vancelucas.com", - "homepage": "https://vancelucas.com/" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", "keywords": [ - "dotenv", - "env", - "environment" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-01-20T15:23:13+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { - "name": "voku/portable-ascii", - "version": "1.5.6", + "name": "symfony/uid", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/voku/portable-ascii.git", - "reference": "80953678b19901e5165c56752d087fc11526017c" + "url": "https://github.com/symfony/uid.git", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", - "reference": "80953678b19901e5165c56752d087fc11526017c", + "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" - }, - "suggest": { - "ext-intl": "Use Intl for transliterator_transliterate() support" + "symfony/console": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "voku\\": "src/voku/" - } + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6514,80 +7665,91 @@ ], "authors": [ { - "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", - "homepage": "https://github.com/voku/portable-ascii", + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", "keywords": [ - "ascii", - "clean", - "php" + "UID", + "ulid", + "uuid" ], "support": { - "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + "source": "https://github.com/symfony/uid/tree/v7.4.0" }, "funding": [ { - "url": "https://www.paypal.me/moelleken", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/voku", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/portable-ascii", - "type": "open_collective" - }, - { - "url": "https://www.patreon.com/voku", - "type": "patreon" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-11-12T00:07:28+00:00" + "time": "2025-09-25T11:02:55+00:00" }, { - "name": "webmozart/assert", - "version": "1.10.0", + "name": "symfony/var-dumper", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" + "symfony/console": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^8.5.13" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, "autoload": { + "files": [ + "Resources/functions/dump.php" + ], "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6595,73 +7757,78 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", "keywords": [ - "assert", - "check", - "validate" + "debug", + "dump" ], "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" - }, - "time": "2021-03-09T10:59:23+00:00" - } - ], - "packages-dev": [ - { - "name": "barryvdh/laravel-debugbar", - "version": "v3.6.2", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/70b89754913fd89fef16d0170a91dbc2a5cd633a", - "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a", - "shasum": "" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" }, - "require": { - "illuminate/routing": "^6|^7|^8", - "illuminate/session": "^6|^7|^8", - "illuminate/support": "^6|^7|^8", - "maximebf/debugbar": "^1.16.3", - "php": ">=7.2", - "symfony/debug": "^4.3|^5", - "symfony/finder": "^4.3|^5" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-27T20:36:44+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" }, "require-dev": { - "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^4|^5|^6", - "phpunit/phpunit": "^8.5|^9.0", - "squizlabs/php_codesniffer": "^3.5" + "symfony/console": "^6.4|^7.0|^8.0" }, + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - }, - "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], - "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facade" - } - } - }, "autoload": { "psr-4": { - "Barryvdh\\Debugbar\\": "src/" + "Symfony\\Component\\Yaml\\": "" }, - "files": [ - "src/helpers.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6670,159 +7837,286 @@ ], "authors": [ { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.2" + "source": "https://github.com/symfony/yaml/tree/v7.4.0" }, "funding": [ { - "url": "https://fruitcake.nl", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/barryvdh", + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2021-06-14T14:29:26+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { - "name": "barryvdh/laravel-ide-helper", - "version": "v2.10.0", + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08" + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/73b1012b927633a1b4cd623c2e6b1678e6faef08", - "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d", "shasum": "" }, "require": { - "barryvdh/reflection-docblock": "^2.0.6", - "composer/composer": "^1.6 || ^2", - "doctrine/dbal": "^2.6 || ^3", - "ext-json": "*", - "illuminate/console": "^8", - "illuminate/filesystem": "^8", - "illuminate/support": "^8", - "nikic/php-parser": "^4.7", - "php": "^7.3 || ^8.0", - "phpdocumentor/type-resolver": "^1.1.0" + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "ext-pdo_sqlite": "*", - "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^8", - "illuminate/view": "^8", - "mockery/mockery": "^1.4", - "orchestra/testbench": "^6", - "phpunit/phpunit": "^8.5 || ^9", - "spatie/phpunit-snapshot-assertions": "^3 || ^4", - "vimeo/psalm": "^3.12" + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + }, + "time": "2024-12-21T16:25:41+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { - "illuminate/events": "Required for automatic helper generation (^6|^7|^8)." + "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.9-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false }, - "laravel": { - "providers": [ - "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" - ] + "branch-alias": { + "dev-master": "5.6-dev" } }, "autoload": { "psr-4": { - "Barryvdh\\LaravelIdeHelper\\": "src" + "Dotenv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" } ], - "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", "keywords": [ - "autocomplete", - "codeintel", - "helper", - "ide", - "laravel", - "netbeans", - "phpdoc", - "phpstorm", - "sublime" + "dotenv", + "env", + "environment" ], "support": { - "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.10.0" + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { - "url": "https://github.com/barryvdh", + "url": "https://github.com/GrahamCampbell", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" } ], - "time": "2021-04-09T06:17:55+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { - "name": "barryvdh/reflection-docblock", - "version": "v2.0.6", + "name": "voku/portable-ascii", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/barryvdh/ReflectionDocBlock.git", - "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16" + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/6b69015d83d3daf9004a71a89f26e27d27ef6a16", - "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.0.0" }, "require-dev": { - "phpunit/phpunit": "~4.0,<4.5" + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" }, "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.10-dev" } }, "autoload": { - "psr-0": { - "Barryvdh": [ - "src/" - ] + "psr-4": { + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6831,49 +8125,77 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], "support": { - "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.0.6" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2018-12-13T10:34:14+00:00" - }, + "time": "2025-10-29T15:56:20+00:00" + } + ], + "packages-dev": [ { - "name": "composer/ca-bundle", - "version": "1.2.10", + "name": "barryvdh/laravel-ide-helper", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8" + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "8d00250cba25728373e92c1d8dcebcbf64623d29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/9fdb22c2e97a614657716178093cd1da90a64aa8", - "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/8d00250cba25728373e92c1d8dcebcbf64623d29", + "reference": "8d00250cba25728373e92c1d8dcebcbf64623d29", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "barryvdh/reflection-docblock": "^2.4", + "composer/class-map-generator": "^1.0", + "ext-json": "*", + "illuminate/console": "^11.15 || ^12", + "illuminate/database": "^11.15 || ^12", + "illuminate/filesystem": "^11.15 || ^12", + "illuminate/support": "^11.15 || ^12", + "php": "^8.2" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" + "ext-pdo_sqlite": "*", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^11.15 || ^12", + "illuminate/view": "^11.15 || ^12", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^9.2 || ^10", + "phpunit/phpunit": "^10.5 || ^11.5.3", + "spatie/phpunit-snapshot-assertions": "^4 || ^5", + "vimeo/psalm": "^5.4", + "vlucas/phpdotenv": "^5" + }, + "suggest": { + "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)." }, "type": "library", "extra": { + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] + }, "branch-alias": { - "dev-main": "1.x-dev" + "dev-master": "3.5-dev" } }, "autoload": { "psr-4": { - "Composer\\CaBundle\\": "src" + "Barryvdh\\LaravelIdeHelper\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -6882,92 +8204,74 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" } ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" + "autocomplete", + "codeintel", + "dev", + "helper", + "ide", + "laravel", + "netbeans", + "phpdoc", + "phpstorm", + "sublime" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.2.10" + "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.6.0" }, "funding": [ { - "url": "https://packagist.com", + "url": "https://fruitcake.nl", "type": "custom" }, { - "url": "https://github.com/composer", + "url": "https://github.com/barryvdh", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2021-06-07T13:58:28+00:00" + "time": "2025-07-17T20:11:57+00:00" }, { - "name": "composer/composer", - "version": "2.1.3", + "name": "barryvdh/reflection-docblock", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/composer/composer.git", - "reference": "fc5c4573aafce3a018eb7f1f8f91cea423970f2e" + "url": "https://github.com/barryvdh/ReflectionDocBlock.git", + "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/fc5c4573aafce3a018eb7f1f8f91cea423970f2e", - "reference": "fc5c4573aafce3a018eb7f1f8f91cea423970f2e", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/d103774cbe7e94ddee7e4870f97f727b43fe7201", + "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201", "shasum": "" }, "require": { - "composer/ca-bundle": "^1.0", - "composer/metadata-minifier": "^1.0", - "composer/semver": "^3.0", - "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^2.0", - "justinrainbow/json-schema": "^5.2.10", - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0", - "react/promise": "^1.2 || ^2.7", - "seld/jsonlint": "^1.4", - "seld/phar-utils": "^1.0", - "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + "php": ">=7.1" }, "require-dev": { - "phpspec/prophecy": "^1.10", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + "phpunit/phpunit": "^8.5.14|^9" }, "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" }, - "bin": [ - "bin/composer" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.3.x-dev" } }, "autoload": { - "psr-4": { - "Composer\\": "src/Composer" + "psr-0": { + "Barryvdh": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -6976,65 +8280,105 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "https://www.naderman.de" - }, + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.0" + }, + "time": "2025-07-17T06:07:30+00:00" + }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" + "name": "Christian Lück", + "email": "christian@clue.engineering" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", - "homepage": "https://getcomposer.org/", + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", "keywords": [ - "autoload", - "dependency", - "package" + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/composer/issues", - "source": "https://github.com/composer/composer/tree/2.1.3" + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" }, "funding": [ { - "url": "https://packagist.com", + "url": "https://clue.engineering/support", "type": "custom" }, { - "url": "https://github.com/composer", + "url": "https://github.com/clue", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2021-06-09T14:31:20+00:00" + "time": "2022-12-23T10:58:28+00:00" }, { - "name": "composer/metadata-minifier", - "version": "1.0.0", + "name": "composer/class-map-generator", + "version": "1.7.0", "source": { "type": "git", - "url": "https://github.com/composer/metadata-minifier.git", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + "url": "https://github.com/composer/class-map-generator.git", + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" }, "require-dev": { - "composer/composer": "^2", - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" }, "type": "library", "extra": { @@ -7044,7 +8388,7 @@ }, "autoload": { "psr-4": { - "Composer\\MetadataMinifier\\": "src" + "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7055,17 +8399,16 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], - "description": "Small utility library that handles metadata minification and expansion.", + "description": "Utilities to scan PHP code and generate class maps.", "keywords": [ - "composer", - "compression" + "classmap" ], "support": { - "issues": "https://github.com/composer/metadata-minifier/issues", - "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.0" }, "funding": [ { @@ -7075,44 +8418,49 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2021-04-07T13:37:33+00:00" + "time": "2025-11-19T10:41:15+00:00" }, { - "name": "composer/semver", - "version": "3.2.5", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7120,33 +8468,22 @@ "MIT" ], "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.5" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -7162,37 +8499,38 @@ "type": "tidelift" } ], - "time": "2021-05-24T12:41:47+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "composer/spdx-licenses", - "version": "1.5.5", + "name": "composer/semver", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "de30328a7af8680efdc03e396aad24befd513200" + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/de30328a7af8680efdc03e396aad24befd513200", - "reference": "de30328a7af8680efdc03e396aad24befd513200", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Composer\\Spdx\\": "src" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7216,16 +8554,17 @@ "homepage": "http://robbast.nl" } ], - "description": "SPDX licenses list and validation library.", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "license", - "spdx", - "validator" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.5" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -7235,35 +8574,33 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2020-12-03T16:04:16+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", - "version": "2.0.1", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", - "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -7287,9 +8624,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.1" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -7300,322 +8637,37 @@ "url": "https://github.com/composer", "type": "github" }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-05-05T19:37:51+00:00" - }, - { - "name": "doctrine/annotations", - "version": "1.13.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", - "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.1" - }, - "time": "2021-05-16T18:07:53+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.0", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2020-11-10T18:47:58+00:00" - }, - { - "name": "facade/flare-client-php", - "version": "1.8.1", - "source": { - "type": "git", - "url": "https://github.com/facade/flare-client-php.git", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f", - "shasum": "" - }, - "require": { - "facade/ignition-contracts": "~1.0", - "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", - "php": "^7.1|^8.0", - "symfony/http-foundation": "^3.3|^4.1|^5.0", - "symfony/mime": "^3.4|^4.0|^5.1", - "symfony/var-dumper": "^3.4|^4.0|^5.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "phpunit/phpunit": "^7.5.16", - "spatie/phpunit-snapshot-assertions": "^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Facade\\FlareClient\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Send PHP errors to Flare", - "homepage": "https://github.com/facade/flare-client-php", - "keywords": [ - "exception", - "facade", - "flare", - "reporting" - ], - "support": { - "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.8.1" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2021-05-31T19:23:29+00:00" - }, - { - "name": "facade/ignition", - "version": "2.10.2", - "source": { - "type": "git", - "url": "https://github.com/facade/ignition.git", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/43688227bbf27c43bc1ad83af224f135b6ef0ff4", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "facade/flare-client-php": "^1.6", - "facade/ignition-contracts": "^1.0.2", - "filp/whoops": "^2.4", - "illuminate/support": "^7.0|^8.0", - "monolog/monolog": "^2.0", - "php": "^7.2.5|^8.0", - "symfony/console": "^5.0", - "symfony/var-dumper": "^5.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "mockery/mockery": "^1.3", - "orchestra/testbench": "^5.0|^6.0", - "psalm/plugin-laravel": "^1.2" - }, - "suggest": { - "laravel/telescope": "^3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Facade\\Ignition\\IgnitionServiceProvider" - ], - "aliases": { - "Flare": "Facade\\Ignition\\Facades\\Flare" - } - } - }, - "autoload": { - "psr-4": { - "Facade\\Ignition\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A beautiful error page for Laravel applications.", - "homepage": "https://github.com/facade/ignition", - "keywords": [ - "error", - "flare", - "laravel", - "page" + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } ], - "support": { - "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", - "forum": "https://twitter.com/flareappio", - "issues": "https://github.com/facade/ignition/issues", - "source": "https://github.com/facade/ignition" - }, - "time": "2021-06-11T06:57:25+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "facade/ignition-contracts", - "version": "1.0.2", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/facade/ignition-contracts.git", - "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267", - "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": ">=7.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.15.8", - "phpunit/phpunit": "^9.3.11", - "vimeo/psalm": "^3.17.1" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Facade\\IgnitionContracts\\": "src" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -7624,64 +8676,58 @@ ], "authors": [ { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://flareapp.io", - "role": "Developer" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "Solution contracts for Ignition", - "homepage": "https://github.com/facade/ignition-contracts", + "description": "Événement is a very simple event dispatching library for PHP", "keywords": [ - "contracts", - "flare", - "ignition" + "event-dispatcher", + "event-emitter" ], "support": { - "issues": "https://github.com/facade/ignition-contracts/issues", - "source": "https://github.com/facade/ignition-contracts/tree/1.0.2" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2020-10-16T08:27:54+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "fakerphp/faker", - "version": "v1.14.1", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1", - "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "psr/container": "^1.0", - "symfony/deprecation-contracts": "^2.2" + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "conflict": { "fzaninotto/faker": "*" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", "ext-intl": "*", - "symfony/phpunit-bridge": "^4.4 || ^5.2" + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" }, "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", "ext-curl": "Required by Faker\\Provider\\Image to download images.", "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v1.15-dev" - } - }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -7704,32 +8750,93 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v.1.14.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, - "time": "2021-03-30T06:27:33+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" }, { "name": "filp/whoops", - "version": "2.13.0", + "version": "2.18.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424" + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/2edbc73a4687d9085c8f20f398eebade844e8424", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1" + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -7769,7 +8876,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.13.0" + "source": "https://github.com/filp/whoops/tree/2.18.4" }, "funding": [ { @@ -7777,88 +8884,76 @@ "type": "github" } ], - "time": "2021-06-04T12:00:00+00:00" + "time": "2025-08-08T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.19.0", + "version": "v3.64.0", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "d5b8a9d852b292c2f8a035200fa6844b1f82300b" + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "58dd9c931c785a79739310aef5178928305ffa67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/d5b8a9d852b292c2f8a035200fa6844b1f82300b", - "reference": "d5b8a9d852b292c2f8a035200fa6844b1f82300b", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67", + "reference": "58dd9c931c785a79739310aef5178928305ffa67", "shasum": "" }, "require": { - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.2 || ^2.0", - "doctrine/annotations": "^1.2", + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.6 || ^7.0 || ^8.0", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.43 || ^4.1.6 || ^5.0", - "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", - "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + "fidry/cpu-core-counter": "^1.0", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.4", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.4.2", - "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.13 || ^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "sanmai/phpunit-legacy-adapter": "^6.4 || ^8.2.1", - "symfony/phpunit-bridge": "^5.2.1", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "ext-mbstring": "For handling non-UTF8 characters." }, "bin": [ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.19-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/Test/IsIdenticalConstraint.php", - "tests/Test/TokensWithObservedTransformers.php", - "tests/TestCase.php" + "exclude-from-classmap": [ + "src/Fixer/Internal/*" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7876,9 +8971,15 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.0" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0" }, "funding": [ { @@ -7886,24 +8987,24 @@ "type": "github" } ], - "time": "2021-05-03T21:43:24+00:00" + "time": "2024-08-30T23:09:38+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -7911,8 +9012,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -7935,44 +9036,32 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { - "name": "justinrainbow/json-schema", - "version": "5.2.10", + "name": "iamcal/sql-parser", + "version": "v0.6", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" + "url": "https://github.com/iamcal/SQLParser.git", + "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", - "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/947083e2dca211a6f12fb1beb67a01e387de9b62", + "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62", "shasum": "" }, - "require": { - "php": ">=5.3.3" - }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^5|^6|^7|^8|^9" }, - "bin": [ - "bin/validate-json" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { - "JsonSchema\\": "src/JsonSchema/" + "iamcal\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7981,83 +9070,57 @@ ], "authors": [ { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" + "name": "Cal Henderson", + "email": "cal@iamcal.com" } ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], + "description": "MySQL schema parser", "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.10" + "issues": "https://github.com/iamcal/SQLParser/issues", + "source": "https://github.com/iamcal/SQLParser/tree/v0.6" }, - "time": "2020-05-27T16:41:55+00:00" + "time": "2025-03-17T16:59:46+00:00" }, { - "name": "laravel/dusk", - "version": "v6.15.0", + "name": "itsgoingd/clockwork", + "version": "v5.3.5", "source": { "type": "git", - "url": "https://github.com/laravel/dusk.git", - "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c" + "url": "https://github.com/itsgoingd/clockwork.git", + "reference": "d928483e231f042dbff9258795cb17aadaebc7d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/45b55fa20321086c4f8cc4e712cbe54db644e21c", - "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/d928483e231f042dbff9258795cb17aadaebc7d0", + "reference": "d928483e231f042dbff9258795cb17aadaebc7d0", "shasum": "" }, "require": { "ext-json": "*", - "ext-zip": "*", - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", - "nesbot/carbon": "^2.0", - "php": "^7.2|^8.0", - "php-webdriver/webdriver": "^1.9.0", - "symfony/console": "^4.3|^5.0", - "symfony/finder": "^4.3|^5.0", - "symfony/process": "^4.3|^5.0", - "vlucas/phpdotenv": "^3.0|^4.0|^5.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.16|^5.17.1|^6.12.1", - "phpunit/phpunit": "^7.5.15|^8.4|^9.0" + "php": ">=7.1" }, "suggest": { - "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + "ext-pdo": "Needed in order to use a SQL database for metadata storage", + "ext-pdo_mysql": "Needed in order to use MySQL for metadata storage", + "ext-pdo_postgres": "Needed in order to use Postgres for metadata storage", + "ext-pdo_sqlite": "Needed in order to use a SQLite for metadata storage", + "ext-redis": "Needed in order to use Redis for metadata storage", + "php-http/discovery": "Vanilla integration - required for the middleware zero-configuration setup" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - }, "laravel": { + "aliases": { + "Clockwork": "Clockwork\\Support\\Laravel\\Facade" + }, "providers": [ - "Laravel\\Dusk\\DuskServiceProvider" + "Clockwork\\Support\\Laravel\\ClockworkServiceProvider" ] } }, "autoload": { "psr-4": { - "Laravel\\Dusk\\": "src/" + "Clockwork\\": "Clockwork/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8066,58 +9129,89 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "itsgoingd", + "email": "itsgoingd@luzer.sk", + "homepage": "https://twitter.com/itsgoingd" } ], - "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "description": "php dev tools in your browser", + "homepage": "https://underground.works/clockwork", "keywords": [ + "Devtools", + "debugging", "laravel", - "testing", - "webdriver" + "logging", + "lumen", + "profiling", + "slim" ], "support": { - "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v6.15.0" + "issues": "https://github.com/itsgoingd/clockwork/issues", + "source": "https://github.com/itsgoingd/clockwork/tree/v5.3.5" }, - "time": "2021-04-06T14:14:57+00:00" + "funding": [ + { + "url": "https://github.com/itsgoingd", + "type": "github" + } + ], + "time": "2025-09-14T15:34:49+00:00" }, { - "name": "maximebf/debugbar", - "version": "v1.16.5", + "name": "larastan/larastan", + "version": "v3.8.0", "source": { "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62" + "url": "https://github.com/larastan/larastan.git", + "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6d51ee9e94cff14412783785e79a4e7ef97b9d62", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62", + "url": "https://api.github.com/repos/larastan/larastan/zipball/d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", + "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", "shasum": "" }, "require": { - "php": "^7.1|^8", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3|^4|^5" + "ext-json": "*", + "iamcal/sql-parser": "^0.6.0", + "illuminate/console": "^11.44.2 || ^12.4.1", + "illuminate/container": "^11.44.2 || ^12.4.1", + "illuminate/contracts": "^11.44.2 || ^12.4.1", + "illuminate/database": "^11.44.2 || ^12.4.1", + "illuminate/http": "^11.44.2 || ^12.4.1", + "illuminate/pipeline": "^11.44.2 || ^12.4.1", + "illuminate/support": "^11.44.2 || ^12.4.1", + "php": "^8.2", + "phpstan/phpstan": "^2.1.29" }, "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^9.4.2" + "doctrine/coding-standard": "^13", + "laravel/framework": "^11.44.2 || ^12.7.2", + "mockery/mockery": "^1.6.12", + "nikic/php-parser": "^5.4", + "orchestra/canvas": "^v9.2.2 || ^10.0.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpunit/phpunit": "^10.5.35 || ^11.5.15" }, "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", + "phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically" }, - "type": "library", + "type": "phpstan-extension", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { - "dev-master": "1.16-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "DebugBar\\": "src/DebugBar/" + "Larastan\\Larastan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8126,61 +9220,67 @@ ], "authors": [ { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" + "name": "Can Vural", + "email": "can9119@gmail.com" } ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", "keywords": [ - "debug", - "debugbar" + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" ], "support": { - "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.5" + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/v3.8.0" }, - "time": "2020-12-07T11:07:24+00:00" + "funding": [ + { + "url": "https://github.com/canvural", + "type": "github" + } + ], + "time": "2025-10-27T23:09:14+00:00" }, { "name": "mockery/mockery", - "version": "1.4.3", + "version": "1.6.12", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea" + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.3 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -8191,12 +9291,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -8214,44 +9322,49 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.4.3" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2021-02-24T09:51:49+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8267,7 +9380,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -8275,39 +9388,42 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", - "version": "v5.4.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "41b7e9999133d5082700d31a1d0977161df8322a" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/41b7e9999133d5082700d31a1d0977161df8322a", - "reference": "41b7e9999133d5082700d31a1d0977161df8322a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "facade/ignition-contracts": "^1.0", - "filp/whoops": "^2.7.2", - "php": "^7.3 || ^8.0", - "symfony/console": "^5.0" + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", + "php": "^8.2.0", + "symfony/console": "^7.1.5" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" }, "require-dev": { - "brianium/paratest": "^6.1", - "fideloper/proxy": "^4.4.1", - "friendsofphp/php-cs-fixer": "^2.17.3", - "fruitcake/laravel-cors": "^2.0.3", - "laravel/framework": "^9.0", - "nunomaduro/larastan": "^0.6.2", - "nunomaduro/mock-final-classes": "^1.0", - "orchestra/testbench": "^7.0", - "phpstan/phpstan": "^0.12.64", - "phpunit/phpunit": "^9.5.0" + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { @@ -8315,9 +9431,15 @@ "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" } }, "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], "psr-4": { "NunoMaduro\\Collision\\": "src/" } @@ -8351,7 +9473,7 @@ }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "url": "https://www.paypal.com/paypalme/enunomaduro", "type": "custom" }, { @@ -8363,24 +9485,25 @@ "type": "patreon" } ], - "time": "2021-04-09T13:38:32+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -8421,22 +9544,28 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2020-06-27T14:33:11+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -8472,1041 +9601,1094 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "php-cs-fixer/diff", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" - }, + "name": "phpstan/phpstan", + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" + "php": "^7.4|^8.0" }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] + "conflict": { + "phpstan/phpstan-shim": "*" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "SpacePossum" - } - ], - "description": "sebastian/diff v2 backport support for PHP5.6", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" + "bin": [ + "phpstan", + "phpstan.phar" ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" - }, - "time": "2020-10-14T08:39:05+00:00" - }, - { - "name": "php-mock/php-mock", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/php-mock/php-mock.git", - "reference": "a3142f257153b71c09bf9146ecf73430b3818b7c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/a3142f257153b71c09bf9146ecf73430b3818b7c", - "reference": "a3142f257153b71c09bf9146ecf73430b3818b7c", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0", - "phpunit/php-text-template": "^1 || ^2" - }, - "replace": { - "malkusch/php-mock": "*" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0", - "squizlabs/php_codesniffer": "^3.5" - }, - "suggest": { - "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." - }, "type": "library", "autoload": { "files": [ - "autoload.php" - ], - "psr-4": { - "phpmock\\": [ - "classes/", - "tests/" - ] - } + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "WTFPL" - ], - "authors": [ - { - "name": "Markus Malkusch", - "email": "markus@malkusch.de", - "homepage": "http://markus.malkusch.de", - "role": "Developer" - } + "MIT" ], - "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", - "homepage": "https://github.com/php-mock/php-mock", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "BDD", - "TDD", - "function", - "mock", - "stub", - "test", - "test double" + "dev", + "static analysis" ], "support": { - "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.3.0" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { - "url": "https://github.com/michalbundyra", + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", "type": "github" } ], - "time": "2020-12-11T19:20:04+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { - "name": "php-mock/php-mock-integration", - "version": "2.1.0", + "name": "phpstan/phpstan-webmozart-assert", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "003d585841e435958a02e9b986953907b8b7609b" + "url": "https://github.com/phpstan/phpstan-webmozart-assert.git", + "reference": "0c641817d2a8f05c7157f92d91986e74d3c8ab0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/003d585841e435958a02e9b986953907b8b7609b", - "reference": "003d585841e435958a02e9b986953907b8b7609b", + "url": "https://api.github.com/repos/phpstan/phpstan-webmozart-assert/zipball/0c641817d2a8f05c7157f92d91986e74d3c8ab0c", + "reference": "0c641817d2a8f05c7157f92d91986e74d3c8ab0c", "shasum": "" }, "require": { - "php": ">=5.6", - "php-mock/php-mock": "^2.2", - "phpunit/php-text-template": "^1 || ^2" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9" + "nikic/php-parser": "^5.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "webmozart/assert": "^1.11.0" }, - "type": "library", - "autoload": { - "psr-4": { - "phpmock\\integration\\": "classes/" + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] } }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "WTFPL" - ], - "authors": [ - { - "name": "Markus Malkusch", - "email": "markus@malkusch.de", - "homepage": "http://markus.malkusch.de", - "role": "Developer" + "autoload": { + "psr-4": { + "PHPStan\\": "src/" } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "description": "Integration package for PHP-Mock", - "homepage": "https://github.com/php-mock/php-mock-integration", - "keywords": [ - "BDD", - "TDD", - "function", - "mock", - "stub", - "test", - "test double" - ], + "description": "PHPStan webmozart/assert extension", "support": { - "issues": "https://github.com/php-mock/php-mock-integration/issues", - "source": "https://github.com/php-mock/php-mock-integration/tree/2.1.0" + "issues": "https://github.com/phpstan/phpstan-webmozart-assert/issues", + "source": "https://github.com/phpstan/phpstan-webmozart-assert/tree/2.0.0" }, - "time": "2020-02-08T14:40:25+00:00" + "time": "2024-10-14T03:45:26+00:00" }, { - "name": "php-mock/php-mock-phpunit", - "version": "2.6.0", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/2877a0e58f12e91b64bf36ccd080a209dcbf6c30", - "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { - "php": ">=7", - "php-mock/php-mock-integration": "^2.1", - "phpunit/phpunit": "^6 || ^7 || ^8 || ^9" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", - "autoload": { - "files": [ - "autoload.php" - ], - "psr-4": { - "phpmock\\phpunit\\": "classes/" + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "WTFPL" + "BSD-3-Clause" ], "authors": [ { - "name": "Markus Malkusch", - "email": "markus@malkusch.de", - "homepage": "http://markus.malkusch.de", - "role": "Developer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", - "homepage": "https://github.com/php-mock/php-mock-phpunit", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "BDD", - "TDD", - "function", - "mock", - "phpunit", - "stub", - "test", - "test double" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.6.0" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, - "time": "2020-02-08T15:44:47+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "php-webdriver/webdriver", - "version": "1.11.1", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/da16e39968f8dd5cfb7d07eef91dc2b731c69880", - "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" - }, - "replace": { - "facebook/webdriver": "*" + "php": ">=8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", - "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^1.1 || ^2.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - }, - "files": [ - "lib/Exception/TimeoutException.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } ], - "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", - "homepage": "https://github.com/php-webdriver/php-webdriver", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "Chromedriver", - "geckodriver", - "php", - "selenium", - "webdriver" + "filesystem", + "iterator" ], "support": { - "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.11.1" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, - "time": "2021-05-21T15:12:49+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "process" ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, - "time": "2020-06-27T09:03:43+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" + "php": ">=8.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-main": "3.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, - "time": "2020-09-03T19:13:55+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "php": ">=8.1" }, "require-dev": { - "ext-tokenizer": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, - "time": "2020-09-17T18:55:26+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.13.0", + "name": "phpunit/phpunit", + "version": "10.5.63", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, - "require-dev": { - "phpspec/phpspec": "^6.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-main": "10.5-dev" } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" + "phpunit", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" }, - "time": "2021-03-17T13:42:18+00:00" + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:48:37+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Async, Promise-based cache interface for ReactPHP", "keywords": [ - "coverage", - "testing", - "xunit" + "cache", + "caching", + "promise", + "reactphp" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2022-11-30T15:59:55+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "name": "react/child-process", + "version": "v0.6.6", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", "shasum": "" }, "require": { - "php": ">=7.3" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\ChildProcess\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ - "filesystem", - "iterator" + "event-driven", + "process", + "reactphp" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2025-01-01T16:37:48+00:00" }, { - "name": "phpunit/php-invoker", - "version": "3.1.1", + "name": "react/dns", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Dns\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "process" + "async", + "dns", + "dns-resolver", + "reactphp" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2025-11-18T19:34:28+00:00" }, { - "name": "phpunit/php-text-template", - "version": "2.0.4", + "name": "react/event-loop", + "version": "v1.6.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\EventLoop\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "template" + "asynchronous", + "event-loop" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { - "name": "phpunit/php-timer", - "version": "5.0.3", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "timer" + "promise", + "promises" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.5.5", + "name": "react/socket", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276" + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89ff45ea9d70e35522fb6654a2ebc221158de276", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.2", - "sebastian/version": "^3.0.2" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, - "bin": [ - "phpunit" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ], - "files": [ - "src/Framework/Assert/Functions.php" - ] + "psr-4": { + "React\\Socket\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "phpunit", - "testing", - "xunit" + "Connection", + "Socket", + "async", + "reactphp", + "stream" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.5" + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" }, "funding": [ { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2021-06-05T04:49:07+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { - "name": "react/promise", - "version": "v2.8.0", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "php": ">=5.4.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36" + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + "React\\Stream\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, { "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "promise", - "promises" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.8.0" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, - "time": "2020-05-12T15:16:56+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -9529,7 +10711,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -9537,32 +10720,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -9585,7 +10768,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -9593,32 +10776,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9640,7 +10823,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -9648,34 +10831,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -9714,41 +10899,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -9771,7 +10969,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" }, "funding": [ { @@ -9779,33 +10978,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -9837,7 +11036,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -9845,27 +11045,27 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -9873,7 +11073,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -9892,7 +11092,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -9900,7 +11100,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" }, "funding": [ { @@ -9908,34 +11109,34 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -9970,53 +11171,63 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -10035,13 +11246,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -10049,33 +11261,33 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -10098,7 +11310,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -10106,34 +11319,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -10155,7 +11368,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -10163,32 +11376,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -10210,7 +11423,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -10218,32 +11431,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -10270,43 +11483,56 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.3", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -10321,14 +11547,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -10336,33 +11563,29 @@ "type": "github" } ], - "abandoned": true, - "time": "2020-09-28T06:45:17+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "sebastian/type", - "version": "2.3.4", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -10381,11 +11604,11 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -10393,88 +11616,115 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "spatie/backtrace", + "version": "1.8.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/spatie/backtrace.git", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", "shasum": "" }, "require": { - "php": ">=7.3" + "php": "^7.3 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } + "require-dev": { + "ext-json": "*", + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Spatie\\Backtrace\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/sponsors/spatie", "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2025-08-26T08:22:30+00:00" }, { - "name": "seld/jsonlint", - "version": "1.8.3", + "name": "spatie/error-solutions", + "version": "1.1.3", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57" + "url": "https://github.com/spatie/error-solutions.git", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9ad6ce79c342fbd44df10ea95511a1b24dee5b57", - "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0 || ^8.0" + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", + "openai-php/client": "^0.10.1", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", + "psr/simple-cache": "^3.0", + "psr/simple-cache-implementation": "^3.0", + "spatie/ray": "^1.28", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" }, - "bin": [ - "bin/jsonlint" - ], "type": "library", "autoload": { "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" + "Spatie\\Ignition\\": "legacy/ignition", + "Spatie\\ErrorSolutions\\": "src", + "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition" } }, "notification-url": "https://packagist.org/downloads/", @@ -10483,115 +11733,147 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "role": "Developer" } ], - "description": "JSON Linter", + "description": "This is my package error-solutions", + "homepage": "https://github.com/spatie/error-solutions", "keywords": [ - "json", - "linter", - "parser", - "validator" + "error-solutions", + "spatie" ], "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.8.3" + "issues": "https://github.com/spatie/error-solutions/issues", + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" }, "funding": [ { - "url": "https://github.com/Seldaek", + "url": "https://github.com/Spatie", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" } ], - "time": "2020-11-11T09:19:24+00:00" + "time": "2025-02-14T12:29:50+00:00" }, { - "name": "seld/phar-utils", - "version": "1.1.1", + "name": "spatie/flare-client-php", + "version": "1.10.1", "source": { "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796" + "url": "https://github.com/spatie/flare-client-php.git", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8674b1d84ffb47cc59a101f5d5a3b61e87d23796", - "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", "shasum": "" }, "require": { - "php": ">=5.3" + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0", + "spatie/backtrace": "^1.6.1", + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/pest-plugin-snapshots": "^1.0|^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "1.3.x-dev" } }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { - "Seld\\PharUtils\\": "src/" + "Spatie\\FlareClient\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "PHAR file format utilities, for when PHP phars you up", + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/spatie/flare-client-php", "keywords": [ - "phar" + "exception", + "flare", + "reporting", + "spatie" ], "support": { - "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/master" + "issues": "https://github.com/spatie/flare-client-php/issues", + "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" }, - "time": "2020-07-07T18:42:57+00:00" + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-02-14T13:42:06+00:00" }, { - "name": "symfony/debug", - "version": "v4.4.25", + "name": "spatie/ignition", + "version": "1.15.1", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "a8d2d5c94438548bff9f998ca874e202bb29d07f" + "url": "https://github.com/spatie/ignition.git", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/a8d2d5c94438548bff9f998ca874e202bb29d07f", - "reference": "a8d2d5c94438548bff9f998ca874e202bb29d07f", + "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85", "shasum": "" }, "require": { - "php": ">=7.1.3", - "psr/log": "~1.0", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "symfony/http-kernel": "<3.4" + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0", + "spatie/error-solutions": "^1.0", + "spatie/flare-client-php": "^1.7", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.20|^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "psr/simple-cache-implementation": "*", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.5.x-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Spatie\\Ignition\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -10599,61 +11881,90 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" } ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", + "description": "A beautiful error page for PHP applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.25" + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/ignition/issues", + "source": "https://github.com/spatie/ignition" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/spatie", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2021-05-26T17:39:37+00:00" + "time": "2025-02-21T14:31:39+00:00" }, { - "name": "symfony/filesystem", - "version": "v5.3.0", + "name": "spatie/laravel-ignition", + "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "348116319d7fb7d1faa781d26a48922428013eb2" + "url": "https://github.com/spatie/laravel-ignition.git", + "reference": "1baee07216d6748ebd3a65ba97381b051838707a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/348116319d7fb7d1faa781d26a48922428013eb2", - "reference": "348116319d7fb7d1faa781d26a48922428013eb2", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a", + "reference": "1baee07216d6748ebd3a65ba97381b051838707a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8" + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.1", + "spatie/ignition": "^1.15", + "symfony/console": "^6.2.3|^7.0", + "symfony/var-dumper": "^6.2.3|^7.0" + }, + "require-dev": { + "livewire/livewire": "^2.11|^3.3.5", + "mockery/mockery": "^1.5.1", + "openai-php/client": "^0.8.1|^0.10", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.34|^3.7", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", + "phpstan/phpstan-phpunit": "^1.3.16|^2.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" }, "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" + }, + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ] + } + }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Spatie\\LaravelIgnition\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -10661,54 +11972,50 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" } ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.3.0" + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/laravel-ignition/issues", + "source": "https://github.com/spatie/laravel-ignition" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/spatie", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-02-20T13:13:55+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5" + "reference": "b38026df55197f9e39a44f3215788edf83187b80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/162e886ca035869866d233a2bfef70cc28f9bbe5", - "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", + "reference": "b38026df55197f9e39a44f3215788edf83187b80", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -10741,7 +12048,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" }, "funding": [ { @@ -10752,40 +12059,52 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.20.0", + "name": "symfony/polyfill-php81", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, - "type": "metapackage", + "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -10800,7 +12119,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -10809,7 +12128,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -10820,30 +12139,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T14:02:19+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "313d02f59d6543311865007e5ff4ace05b35ee65" + "reference": "8a24af0a2e8a872fb745047180649b8418303084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/313d02f59d6543311865007e5ff4ace05b35ee65", - "reference": "313d02f59d6543311865007e5ff4ace05b35ee65", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", + "reference": "8a24af0a2e8a872fb745047180649b8418303084", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -10871,7 +12194,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" }, "funding": [ { @@ -10882,25 +12205,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -10929,7 +12256,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -10937,21 +12264,26 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0", + "php": "^8.2 || ^8.3", "ext-json": "*", "ext-mbstring": "*", + "ext-pdo": "*", "ext-pdo_mysql": "*", + "ext-posix": "*", "ext-zip": "*" }, - "platform-dev": [], - "plugin-api-version": "2.1.0" + "platform-dev": {}, + "platform-overrides": { + "php": "8.2.0" + }, + "plugin-api-version": "2.9.0" } diff --git a/config/activity.php b/config/activity.php new file mode 100644 index 0000000000..6c492d3342 --- /dev/null +++ b/config/activity.php @@ -0,0 +1,12 @@ + env('APP_ACTIVITY_PRUNE_DAYS', 90), + + // If set to true activity log entries generated by an admin user that is not also + // a part of the server in question will be hidden from the activity logs API response. + // + // Activity will still be properly tracked, just not displayed. + 'hide_admin_activity' => env('APP_ACTIVITY_HIDE_ADMIN', false), +]; diff --git a/config/app.php b/config/app.php index f57036f959..475591a0db 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,6 @@ env('APP_NAME', 'Pterodactyl'), @@ -30,7 +32,7 @@ | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various - | services your application utilizes. Set this in your ".env" file. + | services the application utilizes. Set this in your ".env" file. | */ @@ -47,7 +49,7 @@ | */ - 'debug' => env('APP_DEBUG', false), + 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- @@ -56,7 +58,7 @@ | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of - | your application so that it is used when running Artisan tasks. + | the application so that it's available within Artisan commands. | */ @@ -68,8 +70,8 @@ |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. We have gone - | ahead and set this to a sensible default for you out of the box. + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. | */ @@ -81,40 +83,55 @@ |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used - | by the translation service provider. You are free to set this value - | to any of the locales which will be supported by the application. + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. | */ 'locale' => env('APP_LOCALE', 'en'), + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + /* |-------------------------------------------------------------------------- - | Application Fallback Locale + | Encryption Key |-------------------------------------------------------------------------- | - | The fallback locale determines the locale to use when the current one - | is not available. You may change the value to correspond to any of - | the language folders that are provided through your application. + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. | */ - 'fallback_locale' => 'en', + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], /* |-------------------------------------------------------------------------- - | Encryption Key + | Maintenance Mode Driver |-------------------------------------------------------------------------- | - | This key is used by the Illuminate encrypter service and should be set - | to a random, 32 character string, otherwise these encrypted strings - | will not be safe. Please do this before deploying an application! + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" | */ - 'key' => env('APP_KEY'), - - 'cipher' => 'AES-256-CBC', + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], /* |-------------------------------------------------------------------------- @@ -173,6 +190,7 @@ /* * Application Service Providers... */ + Pterodactyl\Providers\ActivityLogServiceProvider::class, Pterodactyl\Providers\AppServiceProvider::class, Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\BackupsServiceProvider::class, @@ -196,47 +214,19 @@ | | This array of class aliases will be registered when this application | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded so they don't hinder performance. + | the aliases are "lazy" loaded, so they don't hinder performance. | */ - 'aliases' => [ + 'aliases' => Illuminate\Support\Facades\Facade::defaultAliases()->merge([ 'Alert' => Prologue\Alerts\Facades\Alert::class, - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, 'Carbon' => Carbon\Carbon::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, - 'Javascript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, + 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - ], + + // Custom Facades + 'Activity' => Pterodactyl\Facades\Activity::class, + 'LogBatch' => Pterodactyl\Facades\LogBatch::class, + 'LogTarget' => Pterodactyl\Facades\LogTarget::class, + ])->toArray(), ]; diff --git a/config/auth.php b/config/auth.php index 02f4807e4a..76db065714 100644 --- a/config/auth.php +++ b/config/auth.php @@ -11,6 +11,7 @@ | password incorrectly. | */ + 'lockout' => [ 'time' => 2, 'attempts' => 3, @@ -21,8 +22,8 @@ | Authentication Defaults |-------------------------------------------------------------------------- | - | This option controls the default authentication "guard" and password - | reset options for your application. You may change these defaults + | This option defines the default authentication "guard" and password + | reset "broker" for your application. You may change these values | as required, but they're a perfect start for most applications. | */ @@ -39,11 +40,11 @@ | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you - | here which uses session storage and the Eloquent user provider. + | which utilizes session storage plus the Eloquent user provider. | - | All authentication drivers have a user provider. This defines how the + | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. + | system used by the application. Typically, Eloquent is utilized. | | Supported: "session", "token" | @@ -66,12 +67,12 @@ | User Providers |-------------------------------------------------------------------------- | - | All authentication drivers have a user provider. This defines how the + | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. + | system used by the application. Typically, Eloquent is utilized. | | If you have multiple user tables or models you may configure multiple - | sources which represent each model / table. These sources may then + | providers to represent the model / table. These providers may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" @@ -90,18 +91,18 @@ | Resetting Passwords |-------------------------------------------------------------------------- | - | Here you may set the options for resetting passwords including the view - | that is your password reset e-mail. You may also set the name of the - | table that maintains all of the reset tokens for your application. - | - | You may specify multiple password reset configurations if you have more - | than one user table or model in the application and you want to have - | separate password reset settings based on the specific user types. + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. | - | The expire time is the number of minutes that the reset token should be + | The expiry time is the number of minutes that each reset token will be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | */ 'passwords' => [ @@ -109,6 +110,20 @@ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, + 'throttle' => 60, ], ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), ]; diff --git a/config/backups.php b/config/backups.php index 21bddfe7b8..e4825673ea 100644 --- a/config/backups.php +++ b/config/backups.php @@ -10,7 +10,12 @@ // This value is used to determine the lifespan of UploadPart presigned urls that wings // uses to upload backups to S3 storage. Value is in minutes, so this would default to an hour. - 'presigned_url_lifespan' => env('BACKUP_PRESIGNED_URL_LIFESPAN', 60), + 'presigned_url_lifespan' => (int) env('BACKUP_PRESIGNED_URL_LIFESPAN', 60), + + // This value defines the maximal size of a single part for the S3 multipart upload during backups + // The maximal part size must be given in bytes. The default value is 5GB. + // Note that 5GB is the maximum for a single part when using AWS S3. + 'max_part_size' => env('BACKUP_MAX_PART_SIZE', 5 * 1024 * 1024 * 1024), // The time to wait before automatically failing a backup, time is in minutes and defaults // to 6 hours. To disable this feature, set the value to `0`. @@ -54,6 +59,8 @@ 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'use_accelerate_endpoint' => env('AWS_BACKUPS_USE_ACCELERATE', false), + + 'storage_class' => env('AWS_BACKUPS_STORAGE_CLASS'), ], ], ]; diff --git a/config/broadcasting.php b/config/broadcasting.php index 9c4c792def..81add6d891 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -10,9 +10,10 @@ | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | - | Supported: "pusher", "redis", "log", "null" + | Supported: "pusher", "ably", "redis", "log", "null" | */ + 'default' => env('BROADCAST_DRIVER', 'null'), /* @@ -29,9 +30,24 @@ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', - 'key' => env('PUSHER_KEY'), - 'secret' => env('PUSHER_SECRET'), + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'host' => env('PUSHER_HOST', 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), ], 'redis' => [ diff --git a/config/cache.php b/config/cache.php index 27276c2004..ce38d79839 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,20 +1,20 @@ env('CACHE_DRIVER', 'redis'), + 'default' => env('CACHE_STORE', env('CACHE_DRIVER', 'redis')), /* |-------------------------------------------------------------------------- @@ -25,13 +25,12 @@ | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "octane", "null" + | */ 'stores' => [ - 'apc' => [ - 'driver' => 'apc', - ], - 'array' => [ 'driver' => 'array', 'serialize' => false, @@ -39,14 +38,16 @@ 'database' => [ 'driver' => 'database', - 'table' => 'cache', - 'connection' => null, - 'lock_connection' => null, + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), ], 'memcached' => [ @@ -57,7 +58,7 @@ env('MEMCACHED_PASSWORD'), ], 'options' => [ - // Memcached::OPT_CONNECT_TIMEOUT => 2000, + // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ @@ -70,8 +71,8 @@ 'redis' => [ 'driver' => 'redis', - 'connection' => 'default', - 'lock_connection' => 'default', + 'connection' => env('REDIS_CACHE_CONNECTION', 'default'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), ], 'sessions' => [ @@ -90,11 +91,11 @@ | Cache Key Prefix |-------------------------------------------------------------------------- | - | When utilizing a RAM based store such as APC or Memcached, there might - | be other applications utilizing the same cache. So, we'll specify a - | value to get prefixed to all our keys so we can avoid collisions. + | When utilizing the APC, database, memcached, Redis, or DynamoDB cache + | stores there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. | */ - 'prefix' => env('CACHE_PREFIX', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_cache'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_cache_'), ]; diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000000..bf72895e06 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,57 @@ + ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], + + /* + * Matches the request method. `['*']` allows all methods. + */ + 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], + + /* + * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), + + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowed_origins_patterns' => [], + + /* + * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. + */ + 'allowed_headers' => ['*'], + + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], + + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, + + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => true, +]; diff --git a/config/database.php b/config/database.php index a4b01139cd..6b1a18f10a 100644 --- a/config/database.php +++ b/config/database.php @@ -10,8 +10,9 @@ |-------------------------------------------------------------------------- | | Here you may specify which of the database connections below you wish - | to use as your default connection for all database work. Of course - | you may use many connections at once using the Database library. + | to use as your default connection for database operations. This is + | the connection which will be utilized unless another connection + | is explicitly specified when you execute a query / statement. | */ @@ -36,18 +37,19 @@ 'connections' => [ 'mysql' => [ 'driver' => 'mysql', - 'url' => env('DATABASE_URL'), + 'url' => env('DB_URL', env('DATABASE_URL')), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'panel'), 'username' => env('DB_USERNAME', 'pterodactyl'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, - 'strict' => env('DB_STRICT_MODE', false), + 'strict' => env('DB_STRICT_MODE', false), // TODO: true by default + 'engine' => null, 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))), 'sslmode' => env('DB_SSLMODE', 'prefer'), 'options' => extension_loaded('pdo_mysql') ? array_filter([ @@ -58,27 +60,29 @@ ]) : [], ], - /* - | ------------------------------------------------------------------------- - | Test Database Connection - | ------------------------------------------------------------------------- - | - | This connection is used by the integration and HTTP tests for Pterodactyl - | development. Normal users of the Panel do not need to adjust any settings - | in here. - */ - 'testing' => [ - 'driver' => 'mysql', - 'host' => env('TESTING_DB_HOST', '127.0.0.1'), - 'port' => env('TESTING_DB_PORT', '3306'), - 'database' => env('TESTING_DB_DATABASE', 'panel_test'), - 'username' => env('TESTING_DB_USERNAME', 'pterodactyl_test'), - 'password' => env('TESTING_DB_PASSWORD', ''), - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => '', - 'strict' => false, + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL', env('DATABASE_URL')), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'panel'), + 'username' => env('DB_USERNAME', 'pterodactyl'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => env('DB_PREFIX', ''), + 'prefix_indexes' => true, + 'strict' => env('DB_STRICT_MODE', true), + 'engine' => null, 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))), + 'sslmode' => env('DB_SSLMODE', 'prefer'), + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + PDO::MYSQL_ATTR_SSL_CERT => env('MYSQL_ATTR_SSL_CERT'), + PDO::MYSQL_ATTR_SSL_KEY => env('MYSQL_ATTR_SSL_KEY'), + PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true), + ]) : [], ], ], @@ -89,11 +93,14 @@ | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of - | the migrations on disk haven't actually been run in the database. + | the migrations on disk haven't actually been run on the database. | */ - 'migrations' => 'migrations', + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], /* |-------------------------------------------------------------------------- @@ -101,8 +108,8 @@ |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also - | provides a richer set of commands than a typical key-value systems - | such as APC or Memcached. Laravel makes it easy to dig right in. + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. | */ @@ -111,13 +118,14 @@ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_database_'), ], 'default' => [ 'scheme' => env('REDIS_SCHEME', 'tcp'), 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), + 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DATABASE', 0), @@ -136,6 +144,7 @@ 'scheme' => env('REDIS_SCHEME', 'tcp'), 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), + 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DATABASE_SESSIONS', 1), diff --git a/config/debugbar.php b/config/debugbar.php deleted file mode 100644 index f1a1fd7537..0000000000 --- a/config/debugbar.php +++ /dev/null @@ -1,167 +0,0 @@ - null, - - /* - |-------------------------------------------------------------------------- - | Storage settings - |-------------------------------------------------------------------------- - | - | DebugBar stores data for session/ajax requests. - | You can disable this, so the debugbar stores data in headers/session, - | but this can cause problems with large data collectors. - | By default, file storage (in the storage folder) is used. Redis and PDO - | can also be used. For PDO, run the package migrations first. - | - */ - 'storage' => [ - 'enabled' => true, - 'driver' => env('DEBUGBAR_DRIVER', 'file'), // redis, file, pdo - 'path' => storage_path() . '/debugbar', // For file driver - 'connection' => null, // Leave null for default connection (Redis/PDO) - ], - - /* - |-------------------------------------------------------------------------- - | Vendors - |-------------------------------------------------------------------------- - | - | Vendor files are included by default, but can be set to false. - | This can also be set to 'js' or 'css', to only include javascript or css vendor files. - | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) - | and for js: jquery and and highlight.js - | So if you want syntax highlighting, set it to true. - | jQuery is set to not conflict with existing jQuery scripts. - | - */ - - 'include_vendors' => true, - - /* - |-------------------------------------------------------------------------- - | Capture Ajax Requests - |-------------------------------------------------------------------------- - | - | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), - | you can use this option to disable sending the data through the headers. - | - */ - - 'capture_ajax' => true, - - /* - |-------------------------------------------------------------------------- - | Clockwork integration - |-------------------------------------------------------------------------- - | - | The Debugbar can emulate the Clockwork headers, so you can use the Chrome - | Extension, without the server-side code. It uses Debugbar collectors instead. - | - */ - 'clockwork' => false, - - /* - |-------------------------------------------------------------------------- - | DataCollectors - |-------------------------------------------------------------------------- - | - | Enable/disable DataCollectors - | - */ - - 'collectors' => [ - 'phpinfo' => true, // Php version - 'messages' => true, // Messages - 'time' => true, // Time Datalogger - 'memory' => true, // Memory usage - 'exceptions' => true, // Exception displayer - 'log' => true, // Logs from Monolog (merged in messages if enabled) - 'db' => true, // Show database (PDO) queries and bindings - 'views' => true, // Views with their data - 'route' => true, // Current route information - 'laravel' => false, // Laravel version and environment - 'events' => true, // All events fired - 'default_request' => false, // Regular or special Symfony request logger - 'symfony_request' => true, // Only one can be enabled.. - 'mail' => true, // Catch mail messages - 'logs' => false, // Add the latest log messages - 'files' => false, // Show the included files - 'config' => false, // Display config settings - 'auth' => false, // Display Laravel authentication status - 'gate' => false, // Display Laravel Gate checks - 'session' => true, // Display session data - ], - - /* - |-------------------------------------------------------------------------- - | Extra options - |-------------------------------------------------------------------------- - | - | Configure some DataCollectors - | - */ - - 'options' => [ - 'auth' => [ - 'show_name' => false, // Also show the users name/email in the debugbar - ], - 'db' => [ - 'with_params' => true, // Render SQL with the parameters substituted - 'timeline' => true, // Add the queries to the timeline - 'backtrace' => true, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. - 'explain' => [ // EXPERIMENTAL: Show EXPLAIN output on queries - 'enabled' => false, - 'types' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ - ], - 'hints' => false, // Show hints for common mistakes - ], - 'mail' => [ - 'full_log' => false, - ], - 'views' => [ - 'data' => false, //Note: Can slow down the application, because the data can be quite large.. - ], - 'route' => [ - 'label' => true, // show complete route on bar - ], - 'logs' => [ - 'file' => null, - ], - ], - - /* - |-------------------------------------------------------------------------- - | Inject Debugbar in Response - |-------------------------------------------------------------------------- - | - | Usually, the debugbar is added just before , by listening to the - | Response after the App is done. If you disable this, you have to add them - | in your template yourself. See http://phpdebugbar.com/docs/rendering.html - | - */ - - 'inject' => true, - - /* - |-------------------------------------------------------------------------- - | DebugBar route prefix - |-------------------------------------------------------------------------- - | - | Sometimes you want to set route prefix to be used by DebugBar to load - | its resources from. Usually the need comes from misconfigured web server or - | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 - | - */ - 'route_prefix' => '_debugbar', -]; diff --git a/config/filesystems.php b/config/filesystems.php index bffcc6514d..917a75681f 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -7,42 +7,32 @@ |-------------------------------------------------------------------------- | | Here you may specify the default filesystem disk that should be used - | by the framework. A "local" driver, as well as a variety of cloud - | based drivers are available for your choosing. Just store away! + | by the framework. The "local" disk, as well as a variety of cloud + | based disks are available to your application for file storage. | */ - 'default' => env('FILESYSTEM_DRIVER', 'local'), - - /* - |-------------------------------------------------------------------------- - | Default Cloud Filesystem Disk - |-------------------------------------------------------------------------- - | - | Many applications store files both locally and in the cloud. For this - | reason, you may specify a default "cloud" driver here. This driver - | will be bound as the Cloud disk implementation in the container. - | - */ - - 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + 'default' => env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | - | Here you may configure as many filesystem "disks" as you wish, and you - | may even configure multiple disks of the same driver. Defaults have - | been setup for each driver as an example of the required options. + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. | - | Supported Drivers: "local", "ftp", "s3", "rackspace" + | Supported drivers: "local", "ftp", "sftp", "s3" | */ + 'disks' => [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, ], 'public' => [ @@ -50,6 +40,7 @@ 'root' => storage_path('app/public'), 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', + 'throw' => false, ], 's3' => [ @@ -58,8 +49,25 @@ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, ], ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], ]; diff --git a/config/hashing.php b/config/hashing.php index f45a6090ba..552f2bbb46 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -10,7 +10,7 @@ | passwords for your application. By default, the bcrypt algorithm is | used; however, you remain free to modify this option if you wish. | - | Supported: "bcrypt", "argon" + | Supported: "bcrypt", "argon", "argon2id" | */ @@ -43,8 +43,8 @@ */ 'argon' => [ - 'memory' => 1024, - 'threads' => 2, - 'time' => 2, + 'memory' => 65536, + 'threads' => 1, + 'time' => 4, ], ]; diff --git a/config/http.php b/config/http.php index ed54e475b1..e76ec923da 100644 --- a/config/http.php +++ b/config/http.php @@ -13,9 +13,9 @@ */ 'rate_limit' => [ 'client_period' => 1, - 'client' => env('APP_API_CLIENT_RATELIMIT', 720), + 'client' => env('APP_API_CLIENT_RATELIMIT', 256), 'application_period' => 1, - 'application' => env('APP_API_APPLICATION_RATELIMIT', 240), + 'application' => env('APP_API_APPLICATION_RATELIMIT', 256), ], ]; diff --git a/config/ide-helper.php b/config/ide-helper.php index 5922f533c8..ada07696df 100644 --- a/config/ide-helper.php +++ b/config/ide-helper.php @@ -1,39 +1,115 @@ '_ide_helper.php', + /* |-------------------------------------------------------------------------- - | Filename & Format + | Models filename |-------------------------------------------------------------------------- | - | The default filename (without extension) and the format (php or json) + | The default filename for the models helper file. | */ - 'filename' => '_ide_helper', - 'format' => 'php', + 'models_filename' => '_ide_helper_models.php', + + /* + |-------------------------------------------------------------------------- + | PhpStorm meta filename + |-------------------------------------------------------------------------- + | + | PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary + | files in it, should you need additional files for your project; e.g. + | `.phpstorm.meta.php/laravel_ide_Helper.php'. + | + */ + 'meta_filename' => '.phpstorm.meta.php', /* |-------------------------------------------------------------------------- | Fluent helpers |-------------------------------------------------------------------------- | - | Set to true to generate commonly used Fluent methods + | Set to true to generate commonly used Fluent methods. + | + */ + + 'include_fluent' => false, + + /* + |-------------------------------------------------------------------------- + | Factory builders + |-------------------------------------------------------------------------- + | + | Set to true to generate factory generators for better factory() + | method auto-completion. + | + | Deprecated for Laravel 8 or latest. + | + */ + + 'include_factory_builders' => false, + + /* + |-------------------------------------------------------------------------- + | Write model magic methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write magic methods of model. + | + */ + + 'write_model_magic_where' => false, + + /* + |-------------------------------------------------------------------------- + | Write model external Eloquent builder methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write external Eloquent builder methods. + | + */ + + 'write_model_external_builder_methods' => true, + + /* + |-------------------------------------------------------------------------- + | Write model relation count and exists properties + |-------------------------------------------------------------------------- + | + | Set to false to disable writing of relation count and exists properties + | to model DocBlocks. | */ - 'include_fluent' => true, + 'write_model_relation_count_properties' => false, + 'write_model_relation_exists_properties' => false, /* |-------------------------------------------------------------------------- - | Write Model Magic methods + | Write Eloquent model mixins |-------------------------------------------------------------------------- | - | Set to false to disable write magic methods of model + | This will add the necessary DocBlock mixins to the model class + | contained in the Laravel framework. This helps the IDE with + | auto-completion. + | + | Please be aware that this setting changes a file within the /vendor directory. | */ - 'write_model_magic_where' => true, + 'write_eloquent_model_mixins' => false, /* |-------------------------------------------------------------------------- @@ -49,6 +125,7 @@ 'helper_files' => [ base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php', ], /* @@ -59,6 +136,9 @@ | Define in which directories the ide-helper:models command should look | for models. | + | glob patterns are supported to easier reach models in sub-directories, + | e.g. `app/Services/* /Models` (without the space). + | */ 'model_locations' => [ @@ -67,71 +147,60 @@ /* |-------------------------------------------------------------------------- - | Extra classes + | Models to ignore |-------------------------------------------------------------------------- | - | These implementations are not really extended, but called with magic functions + | Define which models should be ignored. | */ - 'extra' => [ - 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], - 'Session' => ['Illuminate\Session\Store'], - ], - - 'magic' => [ - 'Log' => [ - 'debug' => 'Monolog\Logger::addDebug', - 'info' => 'Monolog\Logger::addInfo', - 'notice' => 'Monolog\Logger::addNotice', - 'warning' => 'Monolog\Logger::addWarning', - 'error' => 'Monolog\Logger::addError', - 'critical' => 'Monolog\Logger::addCritical', - 'alert' => 'Monolog\Logger::addAlert', - 'emergency' => 'Monolog\Logger::addEmergency', - ], + 'ignored_models' => [ + // App\MyModel::class, ], /* |-------------------------------------------------------------------------- - | Interface implementations + | Models hooks |-------------------------------------------------------------------------- | - | These interfaces will be replaced with the implementing class. Some interfaces - | are detected by the helpers, others can be listed below. + | Define which hook classes you want to run for models to add custom information. + | + | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. | */ - 'interfaces' => [ + 'model_hooks' => [ + // App\Support\IdeHelper\MyModelHook::class ], /* |-------------------------------------------------------------------------- - | Support for custom DB types + | Extra classes |-------------------------------------------------------------------------- | - | This setting allow you to map any custom database type (that you may have - | created using CREATE TYPE statement or imported using database plugin - | / extension to a Doctrine type. + | These implementations are not really extended, but called with magic functions. | - | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: - | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' - | - | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant - | - | The value of the array is an array of type mappings. Key is the name of the custom type, - | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in - | our case it is 'json_array'. Doctrine types are listed here: - | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html - | - | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: + */ + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [], + + /* + |-------------------------------------------------------------------------- + | Interface implementations + |-------------------------------------------------------------------------- | - | "postgresql" => array( - | "jsonb" => "json_array", - | ), + | These interfaces will be replaced with the implementing class. Some interfaces + | are detected by the helpers, others can be listed below. | */ - 'custom_db_types' => [ + + 'interfaces' => [ + // App\MyInterface::class => App\MyImplementation::class, ], /* @@ -147,13 +216,13 @@ | | For example, normally you would see this: | - | * @property \Carbon\Carbon $created_at - | * @property \Carbon\Carbon $updated_at + | * @property \Illuminate\Support\Carbon $created_at + | * @property \Illuminate\Support\Carbon $updated_at | | With this enabled, the properties will be this: | - | * @property \Carbon\Carbon $createdAt - | * @property \Carbon\Carbon $updatedAt + | * @property \Illuminate\Support\Carbon $createdAt + | * @property \Illuminate\Support\Carbon $updatedAt | | Note, it is currently an all-or-nothing option. | @@ -162,7 +231,7 @@ /* |-------------------------------------------------------------------------- - | Property Casts + | Property casts |-------------------------------------------------------------------------- | | Cast the given "real type" to the given "type". @@ -172,4 +241,114 @@ 'integer' => 'int', 'boolean' => 'bool', ], + + /* + |-------------------------------------------------------------------------- + | Include DocBlocks from classes + |-------------------------------------------------------------------------- + | + | Include DocBlocks from classes to allow additional code inspection for + | magic methods and properties. + | + */ + 'include_class_docblocks' => false, + + /* + |-------------------------------------------------------------------------- + | Force FQN usage + |-------------------------------------------------------------------------- + | + | Use the fully qualified (class) name in DocBlocks, + | even if the class exists in the same namespace + | or there is an import (use className) of the class. + | + */ + 'force_fqn' => true, + + /* + |-------------------------------------------------------------------------- + | Use generics syntax + |-------------------------------------------------------------------------- + | + | Use generics syntax within DocBlocks, + | e.g. `Collection` instead of `Collection|User[]`. + | + */ + 'use_generics_annotations' => true, + + /* + |-------------------------------------------------------------------------- + | Default return types for macros + |-------------------------------------------------------------------------- + | + | Define default return types for macros without explicit return types. + | e.g. `\Illuminate\Database\Query\Builder::class => 'static'`, + | `\Illuminate\Support\Str::class => 'string'` + | + */ + 'macro_default_return_types' => [ + Illuminate\Http\Client\Factory::class => Illuminate\Http\Client\PendingRequest::class, + ], + + /* + |-------------------------------------------------------------------------- + | Additional relation types + |-------------------------------------------------------------------------- + | + | Sometimes it's needed to create custom relation types. The key of the array + | is the relationship method name. The value of the array is the fully-qualified + | class name of the relationship, e.g. `'relationName' => RelationShipClass::class`. + | + */ + 'additional_relation_types' => [], + + /* + |-------------------------------------------------------------------------- + | Additional relation return types + |-------------------------------------------------------------------------- + | + | When using custom relation types its possible for the class name to not contain + | the proper return type of the relation. The key of the array is the relationship + | method name. The value of the array is the return type of the relation ('many' + | or 'morphTo'). + | e.g. `'relationName' => 'many'`. + | + */ + 'additional_relation_return_types' => [], + + /* + |-------------------------------------------------------------------------- + | Enforce nullable Eloquent relationships on not null columns + |-------------------------------------------------------------------------- + | + | When set to true (default), this option enforces nullable Eloquent relationships. + | However, in cases where the application logic ensures the presence of related + | records it may be desirable to set this option to false to avoid unwanted null warnings. + | + | Default: true + | A not null column with no foreign key constraint will have a "nullable" relationship. + | * @property int $not_null_column_with_no_foreign_key_constraint + | * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint + | + | Option: false + | A not null column with no foreign key constraint will have a "not nullable" relationship. + | * @property int $not_null_column_with_no_foreign_key_constraint + | * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint + | + */ + + 'enforce_nullable_relationships' => true, + + /* + |-------------------------------------------------------------------------- + | Run artisan commands after migrations to generate model helpers + |-------------------------------------------------------------------------- + | + | The specified commands should run after migrations are finished running. + | + */ + 'post_migrate' => [ + // 'ide-helper:models --nowrite', + ], + ]; diff --git a/config/logging.php b/config/logging.php index bb4470c0fe..4a7a9e0a54 100644 --- a/config/logging.php +++ b/config/logging.php @@ -2,6 +2,8 @@ use Monolog\Handler\NullHandler; use Monolog\Handler\StreamHandler; +use Monolog\Handler\SyslogUdpHandler; +use Monolog\Processor\PsrLogMessageProcessor; return [ /* @@ -9,77 +11,118 @@ | Default Log Channel |-------------------------------------------------------------------------- | - | This option defines the default log channel that gets used when writing - | messages to the logs. The name specified in this option should match - | one of the channels defined in the "channels" configuration array. + | This option defines the default log channel that is utilized to write + | messages to your logs. The value provided here should match one of + | the channels present in the list of "channels" configured below. | */ 'default' => env('LOG_CHANNEL', 'daily'), + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | - | Here you may configure the log channels for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. | - | Available Drivers: "single", "daily", "slack", "syslog", - | "errorlog", "monolog", - | "custom", "stack" + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', - 'channels' => ['single'], + 'channels' => explode(',', env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', - 'days' => 7, + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 7), + 'replace_placeholders' => true, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => 'Laravel Log', - 'emoji' => ':boom:', - 'level' => 'critical', + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], ], 'stderr' => [ 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], + 'processors' => [PsrLogMessageProcessor::class], ], 'syslog' => [ 'driver' => 'syslog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, ], 'errorlog' => [ 'driver' => 'errorlog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], ], ]; diff --git a/config/mail.php b/config/mail.php index f47793438a..a62b40bdac 100644 --- a/config/mail.php +++ b/config/mail.php @@ -3,126 +3,97 @@ return [ /* |-------------------------------------------------------------------------- - | Mail Driver + | Default Mailer |-------------------------------------------------------------------------- | - | Laravel supports both SMTP and PHP's "mail" function as drivers for the - | sending of e-mail. You may specify which one you're using throughout - | your application here. By default, Laravel is setup for SMTP mail. - | - | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | This option controls the default mailer that is used to send any email + | messages sent by your application. Alternative mailers may be setup + | and used as needed; however, this mailer will be used by default. | */ - 'driver' => env('MAIL_DRIVER', 'smtp'), + 'default' => env('MAIL_MAILER', env('MAIL_DRIVER', 'smtp')), /* |-------------------------------------------------------------------------- - | SMTP Host Address + | Mailer Configurations |-------------------------------------------------------------------------- | - | Here you may provide the host address of the SMTP server used by your - | applications. A default option is provided that is compatible with - | the Mailgun mail service which will provide reliable deliveries. + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. | - */ - - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), - - /* - |-------------------------------------------------------------------------- - | SMTP Host Port - |-------------------------------------------------------------------------- + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. | - | This is the SMTP port used by your application to deliver e-mails to - | users of the application. Like the host we have set this value to - | stay compatible with the Mailgun e-mail application by default. + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "log", "array", "failover" | */ - 'port' => env('MAIL_PORT', 587), - - /* - |-------------------------------------------------------------------------- - | Global "From" Address - |-------------------------------------------------------------------------- - | - | You may wish for all e-mails sent by your application to be sent from - | the same address. Here, you may specify a name and address that is - | used globally for all e-mails that are sent by your application. - | - */ - - 'from' => [ - 'address' => env('MAIL_FROM'), - 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), - ], - - /* - |-------------------------------------------------------------------------- - | E-Mail Encryption Protocol - |-------------------------------------------------------------------------- - | - | Here you may specify the encryption protocol that should be used when - | the application send e-mail messages. A sensible default using the - | transport layer security protocol should provide great security. - | - */ + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', env('SERVER_NAME')), + ], - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'ses' => [ + 'transport' => 'ses', + ], - /* - |-------------------------------------------------------------------------- - | SMTP Server Username - |-------------------------------------------------------------------------- - | - | If your SMTP server requires a username for authentication, you should - | set it here. This will get used to authenticate with your server on - | connection. You may also set the "password" value below this one. - | - */ + 'mailgun' => [ + 'transport' => 'mailgun', + ], - 'username' => env('MAIL_USERNAME'), + 'postmark' => [ + 'transport' => 'postmark', + ], - /* - |-------------------------------------------------------------------------- - | SMTP Server Password - |-------------------------------------------------------------------------- - | - | Here you may set the password required by your SMTP server to send out - | messages from your application. This will be given to the server on - | connection so that the application will be able to send messages. - | - */ + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], - 'password' => env('MAIL_PASSWORD'), + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], - /* - |-------------------------------------------------------------------------- - | Sendmail System Path - |-------------------------------------------------------------------------- - | - | When using the "sendmail" driver to send e-mails, we will need to know - | the path to where Sendmail lives on this server. A default path has - | been provided here, which will work well on most of your systems. - | - */ + 'array' => [ + 'transport' => 'array', + ], - 'sendmail' => '/usr/sbin/sendmail -bs', + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + ], /* |-------------------------------------------------------------------------- - | Mail "Pretend" + | Global "From" Address |-------------------------------------------------------------------------- | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. | */ - 'pretend' => false, + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', env('MAIL_FROM', 'hello@example.com')), + 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), + ], /* |-------------------------------------------------------------------------- @@ -134,6 +105,7 @@ | of the emails. Or, you may simply stick with the Laravel defaults! | */ + 'markdown' => [ 'theme' => 'default', diff --git a/config/pterodactyl.php b/config/pterodactyl.php index e21d1859f0..23d382a2e2 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -10,6 +10,7 @@ | setup on the panel. When set to true, configurations stored in the | database will not be applied. */ + 'load_environment_only' => (bool) env('APP_ENVIRONMENT_ONLY', false), /* @@ -21,6 +22,7 @@ | author of custom services, and make upgrades easier by identifying | standard Pterodactyl shipped services. */ + 'service' => [ 'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'), ], @@ -32,6 +34,7 @@ | | Should login success and failure events trigger an email to the user? */ + 'auth' => [ '2fa_required' => env('APP_2FA_REQUIRED', 0), '2fa' => [ @@ -49,6 +52,7 @@ | Certain pagination result counts can be configured here and will take | effect globally. */ + 'paginate' => [ 'frontend' => [ 'servers' => env('APP_PAGINATE_FRONT_SERVERS', 15), @@ -64,18 +68,6 @@ ], ], - /* - |-------------------------------------------------------------------------- - | API Options - |-------------------------------------------------------------------------- - | - | Configuration options for the API. - */ - 'api' => [ - 'include_on_list' => env('API_INCLUDE_ON_LIST', false), - 'key_expire_time' => env('API_KEY_EXPIRE_TIME', 60 * 12), - ], - /* |-------------------------------------------------------------------------- | Guzzle Connections @@ -83,36 +75,12 @@ | | Configure the timeout to be used for Guzzle connections here. */ + 'guzzle' => [ 'timeout' => env('GUZZLE_TIMEOUT', 15), 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 5), ], - /* - |-------------------------------------------------------------------------- - | Queue Names - |-------------------------------------------------------------------------- - | - | Configure the names of queues to be used in the database. - */ - 'queues' => [ - 'low' => env('QUEUE_LOW', 'low'), - 'standard' => env('QUEUE_STANDARD', 'standard'), - 'high' => env('QUEUE_HIGH', 'high'), - ], - - /* - |-------------------------------------------------------------------------- - | Task Timers - |-------------------------------------------------------------------------- - | - | The amount of time in minutes before performing certain actions on the system. - */ - 'tasks' => [ - 'clear_log' => env('PTERODACTYL_CLEAR_TASKLOG', 720), - 'delete_server' => env('PTERODACTYL_DELETE_MINUTES', 10), - ], - /* |-------------------------------------------------------------------------- | CDN @@ -121,6 +89,7 @@ | Information for the panel to use when contacting the CDN to confirm | if panel is up to date. */ + 'cdn' => [ 'cache_time' => 60, 'url' => 'https://cdn.pterodactyl.io/releases/latest.json', @@ -133,6 +102,7 @@ | | Allow clients to create their own databases. */ + 'client_features' => [ 'databases' => [ 'enabled' => env('PTERODACTYL_CLIENT_DATABASES_ENABLED', true), @@ -158,26 +128,11 @@ | | This array includes the MIME filetypes that can be edited via the web. */ + 'files' => [ 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 1024 * 1024 * 4), ], - /* - |-------------------------------------------------------------------------- - | JSON Response Routes - |-------------------------------------------------------------------------- - | - | You should not edit this block. These routes are ajax based routes that - | expect content to be returned in JSON format. - */ - 'json_routes' => [ - 'api/*', - 'daemon/*', - 'remote/*', - ], - - 'default_api_version' => 'application/vnd.pterodactyl.v1+json', - /* |-------------------------------------------------------------------------- | Dynamic Environment Variables @@ -191,6 +146,7 @@ | | 'P_SERVER_CREATED_AT' => 'created_at' */ + 'environment_variables' => [ 'P_SERVER_ALLOCATION_LIMIT' => 'allocation_limit', ], @@ -202,7 +158,39 @@ | | This section controls the output format for JS & CSS assets. */ + 'assets' => [ 'use_hash' => env('PTERODACTYL_USE_ASSET_HASH', false), ], + + /* + |-------------------------------------------------------------------------- + | Email Notification Settings + |-------------------------------------------------------------------------- + | + | This section controls what notifications are sent to users. + */ + + 'email' => [ + // Should an email be sent to a server owner once their server has completed it's first install process? + 'send_install_notification' => env('PTERODACTYL_SEND_INSTALL_NOTIFICATION', true), + // Should an email be sent to a server owner whenever their server is reinstalled? + 'send_reinstall_notification' => env('PTERODACTYL_SEND_REINSTALL_NOTIFICATION', true), + ], + + /* + |-------------------------------------------------------------------------- + | Telemetry Settings + |-------------------------------------------------------------------------- + | + | This section controls the telemetry sent by Pterodactyl. + */ + + 'telemetry' => [ + 'enabled' => env('PTERODACTYL_TELEMETRY_ENABLED', true), + ], + + 'features' => [ + 'new_server_identifiers' => (bool) env('PTERODACTYL_USE_SERVER_IDENTIFIERS', false), + ], ]; diff --git a/config/queue.php b/config/queue.php index 02588bb3ec..797be395c9 100644 --- a/config/queue.php +++ b/config/queue.php @@ -3,14 +3,12 @@ return [ /* |-------------------------------------------------------------------------- - | Default Queue Driver + | Default Queue Connection Name |-------------------------------------------------------------------------- | - | Laravel's queue API supports an assortment of back-ends via a single - | API, giving you convenient access to each back-end using the same - | syntax for each one. Here you may set the default queue driver. - | - | Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | Laravel's queue supports a variety of backends via a single, unified + | API, giving you convenient access to each backend using identical + | syntax for each. The default queue connection is defined below. | */ @@ -21,9 +19,11 @@ | Queue Connections |-------------------------------------------------------------------------- | - | Here you may configure the connection information for each server that - | is used by your application. A default configuration has been added - | for each back-end shipped with Laravel. You are free to add more. + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | */ @@ -34,25 +34,40 @@ 'database' => [ 'driver' => 'database', - 'table' => 'jobs', - 'queue' => env('QUEUE_STANDARD', 'standard'), - 'retry_after' => 90, + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'standard'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', - 'key' => env('SQS_KEY'), - 'secret' => env('SQS_SECRET'), - 'prefix' => env('SQS_QUEUE_PREFIX'), - 'queue' => env('QUEUE_STANDARD', 'standard'), - 'region' => env('SQS_REGION', 'us-east-1'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', - 'connection' => 'default', - 'queue' => env('QUEUE_STANDARD', 'standard'), - 'retry_after' => 90, + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', env('QUEUE_STANDARD', 'standard')), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, ], ], @@ -62,13 +77,16 @@ |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you - | can control which database and table are used to store the jobs that - | have failed. You may change them to any database / table you wish. + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" | */ 'failed' => [ - 'database' => 'mysql', + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ], ]; diff --git a/config/recaptcha.php b/config/recaptcha.php index 22d7394814..757e184a5b 100644 --- a/config/recaptcha.php +++ b/config/recaptcha.php @@ -9,7 +9,7 @@ /* * API endpoint for recaptcha checks. You should not edit this. */ - 'domain' => 'https://www.google.com/recaptcha/api/siteverify', + 'domain' => env('RECAPTCHA_DOMAIN', 'https://www.google.com/recaptcha/api/siteverify'), /* * Use a custom secret key, we use our public one by default diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000000..ed34382fef --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,64 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Laravel\Sanctum\Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Pterodactyl\Http\Middleware\EncryptCookies::class, + 'verify_csrf_token' => Pterodactyl\Http\Middleware\VerifyCsrfToken::class, + ], +]; diff --git a/config/services.php b/config/services.php index 39b8116c71..f8360cd32c 100644 --- a/config/services.php +++ b/config/services.php @@ -3,33 +3,34 @@ return [ /* |-------------------------------------------------------------------------- - | Third Party Service + | Third Party Services |-------------------------------------------------------------------------- | | This file is for storing the credentials for third party services such - | as Stripe, Mailgun, Mandrill, and others. This file provides a sane - | default location for this type of information, allowing packages - | to have a conventional place to find your various credentials. + | as Mailgun, Postmark, AWS and more. This file provides the de facto + | location for this type of information, allowing packages to have + | a conventional file to locate the various service credentials. | */ - 'mailgun' => [ - 'domain' => env('MAILGUN_DOMAIN'), - 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT'), + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), ], - 'mandrill' => [ - 'secret' => env('MANDRILL_SECRET'), + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], - 'ses' => [ - 'key' => env('SES_KEY'), - 'secret' => env('SES_SECRET'), - 'region' => 'us-east-1', + 'resend' => [ + 'key' => env('RESEND_KEY'), ], - 'sparkpost' => [ - 'secret' => env('SPARKPOST_SECRET'), + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], ], ]; diff --git a/config/session.php b/config/session.php index 058528e5f1..6b684ceef3 100644 --- a/config/session.php +++ b/config/session.php @@ -1,16 +1,19 @@ env('SESSION_LIFETIME', 720), - 'expire_on_close' => false, + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), /* |-------------------------------------------------------------------------- @@ -37,21 +41,21 @@ |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data - | should be encrypted before it is stored. All encryption will be run - | automatically by Laravel and you can use the Session like normal. + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. | */ - 'encrypt' => true, + 'encrypt' => env('SESSION_ENCRYPT', true), /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | - | When using the native session driver, we need a location where session - | files may be stored. A default has been set for you but a different - | location may be specified. This is only needed for file sessions. + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. | */ @@ -68,33 +72,35 @@ | */ - 'connection' => env('SESSION_DRIVER') === 'redis' ? 'sessions' : null, + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | - | When using the "database" session driver, you may specify the table we - | should use to manage the sessions. Of course, a sensible default is - | provided for you; however, you are free to change this as needed. + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. | */ - 'table' => 'sessions', + 'table' => env('SESSION_TABLE', 'sessions'), /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | - | When using the "apc" or "memcached" session drivers, you may specify a - | cache store that should be used for these sessions. This value must - | correspond with one of the application's configured cache stores. + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "apc", "dynamodb", "memcached", "redis" | */ - 'store' => null, + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -114,13 +120,16 @@ | Session Cookie Name |-------------------------------------------------------------------------- | - | Here you may change the name of the cookie used to identify a session - | instance by ID. The name specified here will get used every time a - | new session cookie is created by the framework for every driver. + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. | */ - 'cookie' => env('SESSION_COOKIE', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_session'), + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_session' + ), /* |-------------------------------------------------------------------------- @@ -129,20 +138,20 @@ | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of - | your application but you are free to change this when necessary. + | your application, but you're free to change this when necessary. | */ - 'path' => '/', + 'path' => env('SESSION_PATH', '/'), /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | - | Here you may change the domain of the cookie used to identify a session - | in your application. This will determine which domains the cookie is - | available to in your application. A sensible default has been set. + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain and all subdomains. Typically, this shouldn't be changed. | */ @@ -155,11 +164,11 @@ | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep - | the cookie from being sent to you if it can not be done securely. + | the cookie from being sent to you when it can't be done securely. | */ - 'secure' => env('SESSION_SECURE_COOKIE', false), + 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- @@ -168,11 +177,11 @@ | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through - | the HTTP protocol. You are free to modify this option if needed. + | the HTTP protocol. It's unlikely you should disable this option. | */ - 'http_only' => true, + 'http_only' => env('SESSION_HTTP_ONLY', true), /* |-------------------------------------------------------------------------- @@ -181,11 +190,26 @@ | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- | - | Supported: "lax", "strict" + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". | */ - 'same_site' => env('SESSION_SAMESITE_COOKIE', 'lax'), + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), ]; diff --git a/config/trustedproxy.php b/config/trustedproxy.php index d14bf4a1dd..7e0166af8e 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -24,31 +24,5 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), - - /* - * Or, to trust all proxies that connect - * directly to your server, uncomment this: - */ - // 'proxies' => '*', - - /* - * Or, to trust ALL proxies, including those that - * are in a chain of forwarding, uncomment this: - */ - // 'proxies' => '**', - - /* - * Default Header Names - * - * Change these if the proxy does - * not send the default header names. - * - * Note that headers such as X-Forwarded-For - * are transformed to HTTP_X_FORWARDED_FOR format. - * - * The following are Symfony defaults, found in - * \Symfony\Component\HttpFoundation\Request::$trustedHeaders - */ - 'headers' => \Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES') ?? ''), ]; diff --git a/config/view.php b/config/view.php index 8796b0abcc..24dea7a4aa 100644 --- a/config/view.php +++ b/config/view.php @@ -27,5 +27,8 @@ | */ - 'compiled' => realpath(storage_path('framework/views')), + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), ]; diff --git a/config/vue-i18n-generator.php b/config/vue-i18n-generator.php deleted file mode 100644 index 92a20fd732..0000000000 --- a/config/vue-i18n-generator.php +++ /dev/null @@ -1,50 +0,0 @@ - '/resources/lang', - - /* - |-------------------------------------------------------------------------- - | Laravel translation files - |-------------------------------------------------------------------------- - | - | You can choose which translation files to be generated. - | Note: leave this empty for all the translation files to be generated. - | - */ - - 'langFiles' => [], - - /* - |-------------------------------------------------------------------------- - | Output file - |-------------------------------------------------------------------------- - | - | The javascript path where I will place the generated file. - | Note: the path will be prepended to point to the App directory. - | - */ - 'jsPath' => '/resources/lang/i18n', - 'jsFile' => '/resources/lang/locales.js', - - /* - |-------------------------------------------------------------------------- - | i18n library - |-------------------------------------------------------------------------- - | - | Specify the library you use for localization. - | Options are vue-i18n or vuex-i18n. - | - */ - 'i18nLib' => 'vuex-i18n', -]; diff --git a/database/Factories/AllocationFactory.php b/database/Factories/AllocationFactory.php index 679c8c03eb..4a5eb70c03 100644 --- a/database/Factories/AllocationFactory.php +++ b/database/Factories/AllocationFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use Illuminate\Database\Eloquent\Factories\Factory; @@ -21,7 +22,15 @@ public function definition(): array { return [ 'ip' => $this->faker->unique()->ipv4, - 'port' => $this->faker->unique()->randomNumber(5), + 'port' => $this->faker->unique()->numberBetween(1024, 65535), ]; } + + /** + * Attaches the allocation to a specific server model. + */ + public function forServer(Server $server): self + { + return $this->for($server)->for($server->node); + } } diff --git a/database/Factories/ApiKeyFactory.php b/database/Factories/ApiKeyFactory.php index 1faa4be553..40c78ce266 100644 --- a/database/Factories/ApiKeyFactory.php +++ b/database/Factories/ApiKeyFactory.php @@ -25,7 +25,7 @@ public function definition(): array return [ 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => Str::random(ApiKey::IDENTIFIER_LENGTH), + 'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION), 'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)), 'allowed_ips' => null, 'memo' => 'Test Function Key', diff --git a/database/Factories/EggVariableFactory.php b/database/Factories/EggVariableFactory.php index 53b94c4c2a..c2bce816b4 100644 --- a/database/Factories/EggVariableFactory.php +++ b/database/Factories/EggVariableFactory.php @@ -34,7 +34,7 @@ public function definition(): array /** * Indicate that the egg variable is viewable. */ - public function viewable(): Factory + public function viewable(): static { return $this->state(function (array $attributes) { return [ @@ -46,7 +46,7 @@ public function viewable(): Factory /** * Indicate that the egg variable is editable. */ - public function editable(): Factory + public function editable(): static { return $this->state(function (array $attributes) { return [ diff --git a/database/Factories/LocationFactory.php b/database/Factories/LocationFactory.php index 9e0af5dca3..922149dfd6 100644 --- a/database/Factories/LocationFactory.php +++ b/database/Factories/LocationFactory.php @@ -3,18 +3,13 @@ namespace Database\Factories; use Illuminate\Support\Str; -use Pterodactyl\Models\Location; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Pterodactyl\Models\Location> + */ class LocationFactory extends Factory { - /** - * The name of the factory's corresponding model. - * - * @var string - */ - protected $model = Location::class; - /** * Define the model's default state. */ diff --git a/database/Factories/NodeFactory.php b/database/Factories/NodeFactory.php index 429c870d09..1dbd5d9f5e 100644 --- a/database/Factories/NodeFactory.php +++ b/database/Factories/NodeFactory.php @@ -25,7 +25,7 @@ public function definition(): array return [ 'uuid' => Uuid::uuid4()->toString(), 'public' => true, - 'name' => $this->faker->unique()->firstName, + 'name' => 'FactoryNode_' . Str::random(10), 'fqdn' => $this->faker->unique()->ipv4, 'scheme' => 'http', 'behind_proxy' => false, diff --git a/database/Factories/ServerFactory.php b/database/Factories/ServerFactory.php index d39d614b2d..d89bd6a561 100644 --- a/database/Factories/ServerFactory.php +++ b/database/Factories/ServerFactory.php @@ -38,8 +38,11 @@ public function definition() 'cpu' => 0, 'threads' => null, 'oom_disabled' => 0, + 'startup' => '/bin/bash echo "hello world"', + 'image' => 'foo/bar:latest', 'allocation_limit' => null, 'database_limit' => null, + 'backup_limit' => 0, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]; diff --git a/database/Factories/ServerTransferFactory.php b/database/Factories/ServerTransferFactory.php new file mode 100644 index 0000000000..37e56c6178 --- /dev/null +++ b/database/Factories/ServerTransferFactory.php @@ -0,0 +1,31 @@ + [], + 'new_additional_allocations' => [], + 'successful' => null, + 'archived' => false, + ]; + } +} diff --git a/database/Factories/TaskFactory.php b/database/Factories/TaskFactory.php index f04ebbe8b3..32bf950cca 100644 --- a/database/Factories/TaskFactory.php +++ b/database/Factories/TaskFactory.php @@ -2,25 +2,17 @@ namespace Database\Factories; -use Pterodactyl\Models\Task; use Illuminate\Database\Eloquent\Factories\Factory; class TaskFactory extends Factory { - /** - * The name of the factory's corresponding model. - * - * @var string - */ - protected $model = Task::class; - /** * Define the model's default state. */ public function definition(): array { return [ - 'sequence_id' => $this->faker->randomNumber(1), + 'sequence_id' => $this->faker->numberBetween(1, 10), 'action' => 'command', 'payload' => 'test command', 'time_offset' => 120, diff --git a/database/Factories/UserFactory.php b/database/Factories/UserFactory.php index 8eae7c55d0..8a47746761 100644 --- a/database/Factories/UserFactory.php +++ b/database/Factories/UserFactory.php @@ -4,18 +4,14 @@ use Carbon\Carbon; use Ramsey\Uuid\Uuid; -use Pterodactyl\Models\User; +use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Pterodactyl\Models\User> + */ class UserFactory extends Factory { - /** - * The name of the factory's corresponding model. - * - * @var string - */ - protected $model = User::class; - /** * Define the model's default state. */ @@ -24,10 +20,10 @@ public function definition(): array static $password; return [ - 'external_id' => $this->faker->unique()->isbn10, + 'external_id' => null, 'uuid' => Uuid::uuid4()->toString(), - 'username' => $this->faker->unique()->userName, - 'email' => $this->faker->unique()->safeEmail, + 'username' => $this->faker->userName . '_' . Str::random(10), + 'email' => Str::random(32) . '@example.com', 'name_first' => $this->faker->firstName, 'name_last' => $this->faker->lastName, 'password' => $password ?: $password = bcrypt('password'), @@ -42,12 +38,8 @@ public function definition(): array /** * Indicate that the user is an admin. */ - public function admin(): Factory + public function admin(): static { - return $this->state(function (array $attributes) { - return [ - 'root_admin' => true, - ]; - }); + return $this->state(['root_admin' => true]); } } diff --git a/database/Factories/UserSSHKeyFactory.php b/database/Factories/UserSSHKeyFactory.php new file mode 100644 index 0000000000..ab20b251d2 --- /dev/null +++ b/database/Factories/UserSSHKeyFactory.php @@ -0,0 +1,64 @@ + 'ssh-dss AAAAB3NzaC1kc3MAAACBAPfiWwEFvBOafdUmHDPjXsUttt+65FHSZSCVVeEFOTaL7Y3d0CJyrtck8KS1vmXHSb8QFBY2B1yVSb/reaQvNreWZN3KDYfLbF57/zimBn+IrHrJR+ZglhOxDRHoGPWK7q9jYIrOLwoOjkNKXxz1eOHKUgufFfSNtIRLycEXczLrAAAAFQC6LnBErezotG52jN4JostfC/TfEwAAAIACuTxRzYFDXHAxFICeqqY9w+y+v2yQfdeQ1BgCq2GMagUYfOdqnjizTO9M614r/nXZK1SV10TqhUcQtkJzDQIUtBqzBF5cIC/1cIFKzXi5rNHs8Y4bz/PBD+EbQJdiy+1so1oi790r710bqnkzTravAOJ5rGyfuQRLt+f+kuS9NAAAAIEA7tjGtJuXGUtPIXfnrMYS1iOWryO4irqnvaWfel002/DaGaNjRghNe/cUBYlAsjPhGJ1F7BQlLAY1koliTY6l0svs7ZPBM5QOumrr8OaNXGGVIq/RkkxuZHmRoUL2qH3DGYaktPUn4vFPliiAmGWOHAEu1K6B4g4vG/SKgMRpIvc=', + 'rsa_2048' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC4VVsHFO5MxvCtAPyoKGANWyuwZ4fvllvFog5RJbfpDpw8etDFVGEXl+uRR8p79g9oV7MscoFo6HiWrJc4/4vlP665msjosILdIcbnuzMhvXnKisaGh9zflkpyR3KhUxoHxqYp2q8XtffjKKAHz1a8o7OUG6fwaKIqu+d0PoICZQ==', + 'rsa_4096' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo/YLm2SPSlOIG7AagBSmEe5c0b2PLPzUGFp3gARhD6n6ydBS40TlWzeg2qV95lh6fWBd8LsNgPOFmmuKuNZdBjAGeTY4gxKfHY1vK5/zOI4jPPqAMcCMNfd82aM97kx6dO8Hw1R79OyVpOZylpXLHayVPGHUK37Tpih4W7TeVSMrOqQF9F72lzhwgEtkdjm4gLBL6RpdNXrdnjIaNVnuade0Sb3w384vecZPe+S/997WirOMNy2JU4NdMHEnSjd1/i463RpN96AsXFAu1zl9nrXVhA7DVfSHoigXAqbs/xav8PRpLgAKjYpPohxQ9Nu6tP5jRUhfWdYwNFFp/aWloD/0JdP9LqcBBc9sO9TLkz3fBiUf11VM/QT1UhO84G+ahMxVn95jA472VPUe8uKff69lzbvSavEE6qcQX2TzVKOSi1E26Fzc6IZ/tHEuGEbGFxTsiQ1GysVZ0wr1p6ftd1SVqH5F/oaEK7UO8+xn/syEqaPf6A0eJWRNc0+lHA1sIRjmo9MOBvbkKExkx5JLHgGG81DYDFdZUuHY1BgSxJJcmNWV5BKRm350EbgRngoYI5tB3tCiZVW1PI8qyff9mBae11LY5GPlUeDnPrMvSdCKMIWrg7nC8SbndBCO3Fx4z7G2dTQy4ZmY7Ae9jR4pyg7tTOI3qgl8Z462GZi/jzw==', + 'ed25519' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaXIq09NH4a93EVdrvHYiZ67Wj+GBEBQ9ou4W0qSYm2', + ]; + + /** + * Returns a fake public key for use on the system. + * + * @return array + */ + public function definition() + { + $key = PublicKeyLoader::loadPublicKey(static::$keys['ed25519']); + + return [ + 'name' => $this->faker->name(), + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), + ]; + } + + /** + * Returns a DSA public key. + */ + public function dsa(): self + { + $key = PublicKeyLoader::loadPublicKey(static::$keys['dsa']); + + return $this->state([ + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), + ]); + } + + /** + * Returns an RSA public key, if "weak" is specified a 1024-bit RSA key is returned + * which should fail validation when being stored. + */ + public function rsa(bool $weak = false): self + { + $key = PublicKeyLoader::loadPublicKey(static::$keys[$weak ? 'rsa_2048' : 'rsa_4096']); + + return $this->state([ + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), + ]); + } +} diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index 4c7697cf06..605c1ad919 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -2,59 +2,38 @@ namespace Database\Seeders; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; -use Illuminate\Filesystem\Filesystem; use Pterodactyl\Services\Eggs\Sharing\EggImporterService; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; class EggSeeder extends Seeder { - /** - * @var \Illuminate\Filesystem\Filesystem - */ - private $filesystem; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService - */ - private $importerService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $nestRepository; + protected EggImporterService $importerService; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; + protected EggUpdateImporterService $updateImporterService; /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService + * @var string[] */ - private $updateImporterService; + public static array $import = [ + 'Minecraft', + 'Source Engine', + 'Voice Servers', + 'Rust', + ]; /** * EggSeeder constructor. */ public function __construct( EggImporterService $importerService, - EggRepositoryInterface $repository, EggUpdateImporterService $updateImporterService, - Filesystem $filesystem, - NestRepositoryInterface $nestRepository ) { - $this->filesystem = $filesystem; $this->importerService = $importerService; - $this->repository = $repository; $this->updateImporterService = $updateImporterService; - $this->nestRepository = $nestRepository; } /** @@ -62,72 +41,44 @@ public function __construct( */ public function run() { - $this->getEggsToImport()->each(function ($nest) { - $this->parseEggFiles($this->findMatchingNest($nest)); - }); - } - - /** - * Return a list of eggs to import. - */ - protected function getEggsToImport(): Collection - { - return collect([ - 'Minecraft', - 'Source Engine', - 'Voice Servers', - 'Rust', - ]); - } - - /** - * Find the nest that these eggs should be attached to. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - private function findMatchingNest(string $nestName): Nest - { - return $this->nestRepository->findFirstWhere([ - ['author', '=', 'support@pterodactyl.io'], - ['name', '=', $nestName], - ]); + foreach (static::$import as $nest) { + /* @noinspection PhpParamsInspection */ + $this->parseEggFiles( + Nest::query()->where('author', 'support@pterodactyl.io')->where('name', $nest)->firstOrFail() + ); + } } /** * Loop through the list of egg files and import them. */ - private function parseEggFiles(Nest $nest) + protected function parseEggFiles(Nest $nest) { - $files = $this->filesystem->allFiles(database_path('Seeders/eggs/' . kebab_case($nest->name))); + $files = new \DirectoryIterator(database_path('Seeders/eggs/' . kebab_case($nest->name))); $this->command->alert('Updating Eggs for Nest: ' . $nest->name); - Collection::make($files)->each(function ($file) use ($nest) { - /* @var \Symfony\Component\Finder\SplFileInfo $file */ - $decoded = json_decode($file->getContents()); - if (json_last_error() !== JSON_ERROR_NONE) { - $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); - - return; + /** @var \DirectoryIterator $file */ + foreach ($files as $file) { + if (!$file->isFile() || !$file->isReadable()) { + continue; } + $decoded = json_decode(file_get_contents($file->getRealPath()), true, 512, JSON_THROW_ON_ERROR); $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json'); - try { - $egg = $this->repository->setColumns('id')->findFirstWhere([ - ['author', '=', $decoded->author], - ['name', '=', $decoded->name], - ['nest_id', '=', $nest->id], - ]); + $egg = $nest->eggs() + ->where('author', $decoded['author']) + ->where('name', $decoded['name']) + ->first(); + if ($egg instanceof Egg) { $this->updateImporterService->handle($egg, $file); - - $this->command->info('Updated ' . $decoded->name); - } catch (RecordNotFoundException $exception) { + $this->command->info('Updated ' . $decoded['name']); + } else { $this->importerService->handle($file, $nest->id); - - $this->command->comment('Created ' . $decoded->name); + $this->command->comment('Created ' . $decoded['name']); } - }); + } $this->command->line(''); } diff --git a/database/Seeders/NestSeeder.php b/database/Seeders/NestSeeder.php index eae7ae21b7..4d2803e285 100644 --- a/database/Seeders/NestSeeder.php +++ b/database/Seeders/NestSeeder.php @@ -9,12 +9,12 @@ class NestSeeder extends Seeder { /** - * @var \Pterodactyl\Services\Nests\NestCreationService + * @var NestCreationService */ private $creationService; /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + * @var NestRepositoryInterface */ private $repository; @@ -23,7 +23,7 @@ class NestSeeder extends Seeder */ public function __construct( NestCreationService $creationService, - NestRepositoryInterface $repository + NestRepositoryInterface $repository, ) { $this->creationService = $creationService; $this->repository = $repository; @@ -51,7 +51,7 @@ public function run() * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - private function createMinecraftNest(array $nest = null) + private function createMinecraftNest(?array $nest = null) { if (is_null($nest)) { $this->creationService->handle([ @@ -66,7 +66,7 @@ private function createMinecraftNest(array $nest = null) * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - private function createSourceEngineNest(array $nest = null) + private function createSourceEngineNest(?array $nest = null) { if (is_null($nest)) { $this->creationService->handle([ @@ -81,7 +81,7 @@ private function createSourceEngineNest(array $nest = null) * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - private function createVoiceServersNest(array $nest = null) + private function createVoiceServersNest(?array $nest = null) { if (is_null($nest)) { $this->creationService->handle([ @@ -96,7 +96,7 @@ private function createVoiceServersNest(array $nest = null) * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - private function createRustNest(array $nest = null) + private function createRustNest(?array $nest = null) { if (is_null($nest)) { $this->creationService->handle([ diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index ac3749c8c9..69d3cdfea3 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-11-14T19:23:12+00:00", + "exported_at": "2026-04-05T20:12:58+02:00", "name": "Bungeecord", "author": "support@pterodactyl.io", "description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.", @@ -13,14 +13,16 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 25": "ghcr.io\/pterodactyl\/yolks:java_25", + "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", "config": { "files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \"Listening on \"\r\n}", @@ -42,7 +44,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|alpha_num|between:1,6" + "rules": "required|alpha_num|between:1,6", + "field_type": "text" }, { "name": "Bungeecord Jar File", @@ -51,7 +54,8 @@ "default_value": "bungeecord.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index a077df6d15..b3a2856a4b 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-12-11T22:51:29+00:00", + "exported_at": "2026-04-05T20:12:54+02:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -13,14 +13,16 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_17", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_8" - ], + "docker_images": { + "Java 25": "ghcr.io\/pterodactyl\/yolks:java_25", + "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true $( [[ ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", + "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[ ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", @@ -29,8 +31,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\nif [[ ! -d \/mnt\/server ]]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\n# Remove spaces from the version number to avoid issues with curl\r\nFORGE_VERSION=\"$(echo \"$FORGE_VERSION\" | tr -d ' ')\"\r\nMC_VERSION=\"$(echo \"$MC_VERSION\" | tr -d ' ')\"\r\n\r\nif [[ ! -z ${FORGE_VERSION} ]]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [[ \"${MC_VERSION}\" == \"latest\" ]] || [[ \"${MC_VERSION}\" == \"\" ]]; then\r\n echo -e \"getting latest version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"latest\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n BUILD_TYPE=latest\r\n fi\r\n\r\n if [[ \"${BUILD_TYPE}\" != \"recommended\" ]] && [[ \"${BUILD_TYPE}\" != \"latest\" ]]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [[ \"${VERSION_KEY}\" == \"\" ]] && [[ \"${BUILD_TYPE}\" == \"recommended\" ]]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"latest\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n echo -e \"The install failed because there is no valid version of forge for the version of minecraft selected.\"\r\n exit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [[ \"${MC_VERSION}\" == \"1.7.10\" ]] || [[ \"${MC_VERSION}\" == \"1.8.9\" ]]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [[ \"${MC_VERSION}\" == \"1.7.10\" ]]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\n\r\nif [[ ! -z \"${DOWNLOAD_LINK}\" ]]; then\r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid. Exiting now\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link provided. Exiting now\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [[ ! -f .\/installer.jar ]]; then\r\n echo \"!!! Error downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\nfunction unix_args {\r\n echo -e \"Detected Forge 1.17 or newer version. Setting up forge unix args.\"\r\n ln -sf libraries\/net\/minecraftforge\/forge\/*\/unix_args.txt unix_args.txt\r\n}\r\n\r\n# Delete args to support downgrading\/upgrading\r\nrm -rf libraries\/net\/minecraftforge\/forge\r\nrm unix_args.txt\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed using Forge version ${FORGE_VERSION} and Minecraft version ${MINECRAFT_VERSION}\"; exit 4; }\r\n\r\n# Check if we need a symlink for 1.17+ Forge JPMS args\r\nif [[ $MC_VERSION =~ ^1\\.(17|18|19|20|21|22|23) || $FORGE_VERSION =~ ^1\\.(17|18|19|20|21|22|23) ]]; then\r\n unix_args\r\n\r\n# Check if someone has set MC to latest but overwrote it with older Forge version, otherwise we would have false positives\r\nelif [[ $MC_VERSION == \"latest\" && $FORGE_VERSION =~ ^1\\.(17|18|19|20|21|22|23) ]]; then\r\n unix_args\r\nelse\r\n # For versions below 1.17 that ship with jar\r\n mv $FORGE_JAR $SERVER_JARFILE\r\nfi\r\n\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", - "container": "openjdk:8-jdk-slim", + "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\nif [[ ! -d \/mnt\/server ]]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\n# Remove spaces from the version number to avoid issues with curl\r\nFORGE_VERSION=\"$(echo \"$FORGE_VERSION\" | tr -d ' ')\"\r\nMC_VERSION=\"$(echo \"$MC_VERSION\" | tr -d ' ')\"\r\n\r\nif [[ ! -z ${FORGE_VERSION} ]]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [[ \"${MC_VERSION}\" == \"latest\" ]] || [[ \"${MC_VERSION}\" == \"\" ]]; then\r\n echo -e \"getting latest version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"latest\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n BUILD_TYPE=latest\r\n fi\r\n\r\n if [[ \"${BUILD_TYPE}\" != \"recommended\" ]] && [[ \"${BUILD_TYPE}\" != \"latest\" ]]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [[ \"${VERSION_KEY}\" == \"\" ]] && [[ \"${BUILD_TYPE}\" == \"recommended\" ]]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"latest\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n echo -e \"The install failed because there is no valid version of forge for the version of minecraft selected.\"\r\n exit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [[ \"${MC_VERSION}\" == \"1.7.10\" ]] || [[ \"${MC_VERSION}\" == \"1.8.9\" ]]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [[ \"${MC_VERSION}\" == \"1.7.10\" ]]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\n\r\nif [[ ! -z \"${DOWNLOAD_LINK}\" ]]; then\r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid. Exiting now\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link provided. Exiting now\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [[ ! -f .\/installer.jar ]]; then\r\n echo \"!!! Error downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\nfunction unix_args {\r\n echo -e \"Detected Forge 1.17 or newer version. Setting up forge unix args.\"\r\n ln -sf libraries\/net\/minecraftforge\/forge\/*\/unix_args.txt unix_args.txt\r\n}\r\n\r\n# Delete args to support downgrading\/upgrading\r\nrm -rf libraries\/net\/minecraftforge\/forge\r\nrm unix_args.txt\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"\\nInstall failed using Forge version ${FORGE_VERSION} and Minecraft version ${MINECRAFT_VERSION}.\\nShould you be using unlimited memory value of 0, make sure to increase the default install resource limits in the Wings config or specify exact allocated memory in the server Build Configuration instead of 0! \\nOtherwise, the Forge installer will not have enough memory.\"; exit 4; }\r\n\r\n# Check if we need a symlink for 1.17+ Forge JPMS args\r\nif [[ $MC_VERSION =~ ^1\\.(17|18|19|20|21|22|23) || $FORGE_VERSION =~ ^1\\.(17|18|19|20|21|22|23) ]]; then\r\n unix_args\r\n\r\n# Check if someone has set MC to latest but overwrote it with older Forge version, otherwise we would have false positives\r\nelif [[ $MC_VERSION == \"latest\" && $FORGE_VERSION =~ ^1\\.(17|18|19|20|21|22|23) ]]; then\r\n unix_args\r\nelse\r\n # For versions below 1.17 that ship with jar\r\n mv $FORGE_JAR $SERVER_JARFILE\r\nfi\r\n\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar\r\necho -e \"Installation process is completed\"", + "container": "eclipse-temurin:8-jdk-jammy", "entrypoint": "bash" } }, @@ -42,7 +44,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Minecraft Version", @@ -51,7 +54,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:9" + "rules": "required|string|max:9", + "field_type": "text" }, { "name": "Build Type", @@ -60,16 +64,18 @@ "default_value": "recommended", "user_viewable": true, "user_editable": true, - "rules": "required|string|in:recommended,latest" + "rules": "required|string|in:recommended,latest", + "field_type": "text" }, { "name": "Forge Version", - "description": "Gets an exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", + "description": "The full exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", "env_variable": "FORGE_VERSION", "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:25" + "rules": "nullable|regex:\/^[0-9\\.\\-]+$\/", + "field_type": "text" } ] } \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index 39ab216659..26c0880c00 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-03-11T10:21:01-05:00", + "exported_at": "2026-03-26T10:34:12+01:00", "name": "Paper", "author": "parker@pterodactyl.io", "description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.", @@ -13,14 +13,16 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 25": "ghcr.io\/pterodactyl\/yolks:java_25", + "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", @@ -29,7 +31,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi", + "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\nUSER_AGENT=\"Pterodactyl (https:\/\/Pterodactyl.io)\"\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl --user-agent \"${USER_AGENT}\" -s https:\/\/fill.papermc.io\/v3\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions | any(.[]; index($VERSION))' | grep -m1 true`\r\n\tLATEST_VERSION=`curl --user-agent \"${USER_AGENT}\" -s https:\/\/fill.papermc.io\/v3\/projects\/${PROJECT} | jq -r '.versions | to_entries | .[0].value[0]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl --user-agent \"${USER_AGENT}\" -s https:\/\/fill.papermc.io\/v3\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl --user-agent \"${USER_AGENT}\" -s https:\/\/fill.papermc.io\/v3\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[0]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"Project: ${PROJECT}\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\tDOWNLOAD_URL=`curl --user-agent \"${USER_AGENT}\" -s https:\/\/fill.papermc.io\/v3\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER} | jq -r '.downloads.\"server:default\".url'`\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl --user-agent \\\"${USER_AGENT}\\\" -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl --user-agent \"${USER_AGENT}\" -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl --user-agent \"${USER_AGENT}\" -o server.properties https:\/\/raw.githubusercontent.com\/pterodactyl\/game-eggs\/main\/minecraft\/java\/server.properties\r\nfi", "container": "ghcr.io\/pterodactyl\/installers:alpine", "entrypoint": "ash" } @@ -42,7 +44,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:20" + "rules": "nullable|string|max:20", + "field_type": "text" }, { "name": "Server Jar File", @@ -51,7 +54,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Download Path", @@ -60,7 +64,8 @@ "default_value": "", "user_viewable": false, "user_editable": false, - "rules": "nullable|string" + "rules": "nullable|string", + "field_type": "text" }, { "name": "Build Number", @@ -69,7 +74,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|max:20", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json index 908c1f4e55..1b9a8500c3 100644 --- a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json +++ b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-10-22T19:19:17+02:00", + "exported_at": "2026-04-05T20:12:50+02:00", "name": "Sponge (SpongeVanilla)", "author": "support@pterodactyl.io", "description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.", @@ -13,13 +13,15 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16" - ], + "docker_images": { + "Java 25": "ghcr.io\/pterodactyl\/yolks:java_25", + "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", @@ -41,7 +43,8 @@ "default_value": "1.12.2-7.3.0", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/" + "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/", + "field_type": "text" }, { "name": "Server Jar File", @@ -50,7 +53,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json index 2361a29746..c09b8045de 100644 --- a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-11-14T19:18:30+00:00", + "exported_at": "2026-04-05T20:12:45+02:00", "name": "Vanilla Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.", @@ -13,14 +13,16 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 25": "ghcr.io\/pterodactyl\/yolks:java_25", + "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", @@ -42,7 +44,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Server Version", @@ -51,7 +54,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|between:3,15" + "rules": "required|string|between:3,15", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index ddbf88cdb5..35f543c75a 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -1,21 +1,21 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-01-18T11:44:55-05:00", + "exported_at": "2023-03-25T13:37:00+00:00", "name": "Rust", "author": "support@pterodactyl.io", "description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.", "features": [ "steam_disk_space" ], - "images": [ - "quay.io\/pterodactyl\/core:rust" - ], + "docker_images": { + "ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust" + }, "file_denylist": [], - "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", + "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", "config": { "files": "{}", "startup": "{\r\n \"done\": \"Server startup complete\"\r\n}", @@ -37,16 +37,18 @@ "default_value": "A Rust Server", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:60" + "rules": "required|string|max:60", + "field_type": "text" }, { - "name": "OxideMod", - "description": "Set whether you want the server to use and auto update OxideMod or not. Valid options are \"1\" for true and \"0\" for false.", - "env_variable": "OXIDE", - "default_value": "0", + "name": "Modding Framework", + "description": "The modding framework to be used: carbon, oxide, vanilla.\r\nDefaults to \"vanilla\" for a non-modded server installation.", + "env_variable": "FRAMEWORK", + "default_value": "vanilla", "user_viewable": true, "user_editable": true, - "rules": "required|boolean" + "rules": "required|in:vanilla,oxide,carbon", + "field_type": "text" }, { "name": "Level", @@ -55,7 +57,8 @@ "default_value": "Procedural Map", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|max:20", + "field_type": "text" }, { "name": "Description", @@ -64,7 +67,8 @@ "default_value": "Powered by Pterodactyl", "user_viewable": true, "user_editable": true, - "rules": "required|string" + "rules": "required|string", + "field_type": "text" }, { "name": "URL", @@ -73,7 +77,8 @@ "default_value": "http:\/\/pterodactyl.io", "user_viewable": true, "user_editable": true, - "rules": "nullable|url" + "rules": "nullable|url", + "field_type": "text" }, { "name": "World Size", @@ -82,7 +87,8 @@ "default_value": "3000", "user_viewable": true, "user_editable": true, - "rules": "required|integer" + "rules": "required|integer", + "field_type": "text" }, { "name": "World Seed", @@ -91,7 +97,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string" + "rules": "nullable|string", + "field_type": "text" }, { "name": "Max Players", @@ -100,7 +107,8 @@ "default_value": "40", "user_viewable": true, "user_editable": true, - "rules": "required|integer" + "rules": "required|integer", + "field_type": "text" }, { "name": "Server Image", @@ -109,7 +117,18 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url" + "rules": "nullable|url", + "field_type": "text" + }, + { + "name": "Query Port", + "description": "Server Query Port. Can't be the same as Game's primary port.", + "env_variable": "QUERY_PORT", + "default_value": "27017", + "user_viewable": true, + "user_editable": false, + "rules": "required|integer", + "field_type": "text" }, { "name": "RCON Port", @@ -118,16 +137,18 @@ "default_value": "28016", "user_viewable": true, "user_editable": false, - "rules": "required|integer" + "rules": "required|integer", + "field_type": "text" }, { "name": "RCON Password", "description": "RCON access password.", "env_variable": "RCON_PASS", - "default_value": "CHANGEME", + "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^[\\w.-]*$\/|max:64" + "rules": "required|regex:\/^[\\w.-]*$\/|max:64", + "field_type": "text" }, { "name": "Save Interval", @@ -136,7 +157,8 @@ "default_value": "60", "user_viewable": true, "user_editable": true, - "rules": "required|integer" + "rules": "required|integer", + "field_type": "text" }, { "name": "Additional Arguments", @@ -145,7 +167,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string" + "rules": "nullable|string", + "field_type": "text" }, { "name": "App Port", @@ -154,7 +177,8 @@ "default_value": "28082", "user_viewable": true, "user_editable": false, - "rules": "required|integer" + "rules": "required|integer", + "field_type": "text" }, { "name": "Server Logo", @@ -163,7 +187,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url" + "rules": "nullable|url", + "field_type": "text" }, { "name": "Custom Map URL", @@ -172,7 +197,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url" + "rules": "nullable|url", + "field_type": "text" } ] } diff --git a/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json b/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json index 584ffe37c4..56af5d0062 100644 --- a/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json +++ b/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json @@ -1,21 +1,21 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-01-18T07:01:38-05:00", + "exported_at": "2025-06-12T18:44:29+02:00", "name": "Ark: Survival Evolved", - "author": "dev@shepper.fr", + "author": "support@pterodactyl.io", "description": "As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! \u2014 Gamepedia: ARK", "features": [ "steam_disk_space" ], - "images": [ - "quay.io\/parkervcp\/pterodactyl-images:debian_source" - ], + "docker_images": { + "ghcr.io\/pterodactyl\/games:source": "ghcr.io\/pterodactyl\/games:source" + }, "file_denylist": [], - "startup": "rmv() { echo -e \"stopping server\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c saveworld && rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c DoExit; }; trap rmv 15; cd ShooterGame\/Binaries\/Linux && .\/ShooterGameServer {{SERVER_MAP}}?listen?SessionName=\"{{SESSION_NAME}}\"?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?RCONPort={{RCON_PORT}}?QueryPort={{QUERY_PORT}}?RCONEnabled=True$( [ \"$BATTLE_EYE\" == \"1\" ] || printf %s ' -NoBattlEye' ) -server {{ARGS}} -log & until echo \"waiting for rcon connection...\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD}; do sleep 5; done", + "startup": "rmv() { echo \"stopping server\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} saveworld &&rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} DoExit && wait ${ARK_PID}; echo \"Server Closed\"; exit; }; trap rmv 15 2; cd ShooterGame\/Binaries\/Linux && .\/ShooterGameServer {{SERVER_MAP}}?listen?SessionName=\"{{SESSION_NAME}}\"?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?RCONPort={{RCON_PORT}}?QueryPort={{QUERY_PORT}}?RCONEnabled=True?MaxPlayers={{MAX_PLAYERS}}?GameModIds={{MOD_ID}}$( [ \"$BATTLE_EYE\" == \"1\" ] || printf %s ' -NoBattlEye' ) -server -automanagedmods {{ARGS}} -log & ARK_PID=$! ; until echo \"waiting for rcon connection...\"; (rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD})<&0 & wait $!; do sleep 5; done", "config": { "files": "{}", "startup": "{\r\n \"done\": \"Waiting commands for 127.0.0.1:\"\r\n}", @@ -24,7 +24,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\n\r\nmkdir -p \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n## create a symbolic link for loading mods\r\ncd \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\nln -sf ..\/..\/..\/..\/..\/Steam\/steamapps steamapps\r\ncd \/mnt\/server", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends --no-install-suggests install curl lib32gcc-s1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\n\r\nmkdir -p \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n## create a symbolic link for loading mods\r\ncd \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\nln -sf ..\/..\/..\/..\/..\/Steam\/steamapps steamapps\r\ncd \/mnt\/server", "container": "ghcr.io\/pterodactyl\/installers:debian", "entrypoint": "bash" } @@ -37,7 +37,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|alpha_dash|between:1,100" + "rules": "nullable|alpha_dash|between:1,100", + "field_type": "text" }, { "name": "Admin Password", @@ -46,16 +47,18 @@ "default_value": "PleaseChangeMe", "user_viewable": true, "user_editable": true, - "rules": "required|alpha_dash|between:1,100" + "rules": "required|alpha_dash|between:1,100", + "field_type": "text" }, { "name": "Server Map", - "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2, LostIsland", + "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2, Fjordur", "env_variable": "SERVER_MAP", "default_value": "TheIsland", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|max:20", + "field_type": "text" }, { "name": "Server Name", @@ -64,7 +67,8 @@ "default_value": "A Pterodactyl Hosted ARK Server", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:128" + "rules": "required|string|max:128", + "field_type": "text" }, { "name": "Rcon Port", @@ -73,7 +77,8 @@ "default_value": "27020", "user_viewable": true, "user_editable": true, - "rules": "required|numeric" + "rules": "required|numeric", + "field_type": "text" }, { "name": "Query Port", @@ -82,7 +87,8 @@ "default_value": "27015", "user_viewable": true, "user_editable": true, - "rules": "required|numeric" + "rules": "required|numeric", + "field_type": "text" }, { "name": "Auto-update server", @@ -91,7 +97,8 @@ "default_value": "0", "user_viewable": true, "user_editable": true, - "rules": "required|boolean" + "rules": "required|boolean", + "field_type": "text" }, { "name": "Battle Eye", @@ -100,7 +107,8 @@ "default_value": "1", "user_viewable": true, "user_editable": true, - "rules": "required|boolean" + "rules": "required|boolean", + "field_type": "text" }, { "name": "App ID", @@ -109,7 +117,8 @@ "default_value": "376030", "user_viewable": true, "user_editable": false, - "rules": "nullable|numeric" + "rules": "nullable|numeric", + "field_type": "text" }, { "name": "Additional Arguments", @@ -118,7 +127,28 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string" + "rules": "nullable|string", + "field_type": "text" + }, + { + "name": "Mods", + "description": "Specifies the order and which mods are loaded. ModIDs need to be comma-separated such as: ModID1,ModID2", + "env_variable": "MOD_ID", + "default_value": "", + "user_viewable": true, + "user_editable": true, + "rules": "nullable|string", + "field_type": "text" + }, + { + "name": "Max Players", + "description": "Specifies the maximum amount of players able to join the server.", + "env_variable": "MAX_PLAYERS", + "default_value": "12", + "user_viewable": true, + "user_editable": true, + "rules": "numeric", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/source-engine/egg-garrys-mod.json b/database/Seeders/eggs/source-engine/egg-garrys-mod.json index 82ec08feef..32829174d0 100644 --- a/database/Seeders/eggs/source-engine/egg-garrys-mod.json +++ b/database/Seeders/eggs/source-engine/egg-garrys-mod.json @@ -87,7 +87,7 @@ }, { "name": "Tickrate", - "description": "The tickrate defines how fast the server will update each entities location.", + "description": "The tickrate defines how fast the server will update each entity's location.", "env_variable": "TICKRATE", "default_value": "22", "user_viewable": true, diff --git a/database/Seeders/eggs/voice-servers/egg-mumble-server.json b/database/Seeders/eggs/voice-servers/egg-mumble-server.json index 27f60e7173..feac4dc1a8 100644 --- a/database/Seeders/eggs/voice-servers/egg-mumble-server.json +++ b/database/Seeders/eggs/voice-servers/egg-mumble-server.json @@ -1,28 +1,28 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-06-15T16:54:54-04:00", + "exported_at": "2022-10-15T12:38:18+02:00", "name": "Mumble Server", "author": "support@pterodactyl.io", "description": "Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.", "features": null, - "images": [ - "ghcr.io\/pterodactyl\/yolks:alpine" - ], + "docker_images": { + "Mumble": "ghcr.io\/parkervcp\/yolks:voice_mumble" + }, "file_denylist": [], - "startup": ".\/murmur.x86 -fg", + "startup": "mumble-server -fg -ini murmur.ini", "config": { - "files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"logfile\": \"murmur.log\",\r\n \"port\": \"{{server.build.default.port}}\",\r\n \"host\": \"0.0.0.0\",\r\n \"users\": \"{{server.build.env.MAX_USERS}}\"\r\n }\r\n }\r\n}", + "files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"database\": \"\/home\/container\/murmur.sqlite\",\r\n \"logfile\": \"\/home\/container\/murmur.log\",\r\n \"port\": \"{{server.build.default.port}}\",\r\n \"host\": \"0.0.0.0\",\r\n \"users\": \"{{server.build.env.MAX_USERS}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \"Server listening on\"\r\n}", - "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/murmur.log\"\r\n}", + "logs": "{}", "stop": "^C" }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Mumble Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nGITHUB_PACKAGE=mumble-voip\/mumble\r\nMATCH=murmur-static\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${GITHUB_USER}\" ] && [ -z \"${GITHUB_OAUTH_TOKEN}\" ] ; then\r\n echo -e \"using anon api call\"\r\nelse\r\n echo -e \"user and oauth token set\"\r\n alias curl='curl -u ${GITHUB_USER}:${GITHUB_OAUTH_TOKEN} '\r\nfi\r\n\r\n## get release info and download links\r\nLATEST_JSON=$(curl --silent \"https:\/\/api.github.com\/repos\/${GITHUB_PACKAGE}\/releases\/latest\")\r\nRELEASES=$(curl --silent \"https:\/\/api.github.com\/repos\/${GITHUB_PACKAGE}\/releases\")\r\n\r\nif [ -z \"${VERSION}\" ] || [ \"${VERSION}\" == \"latest\" ]; then\r\n DOWNLOAD_LINK=$(echo ${LATEST_JSON} | jq .assets | jq -r .[].browser_download_url | grep -m 1 -i ${MATCH})\r\nelse\r\n VERSION_CHECK=$(echo ${RELEASES} | jq -r --arg VERSION \"${VERSION}\" '.[] | select(.tag_name==$VERSION) | .tag_name')\r\n if [ \"${VERSION}\" == \"${VERSION_CHECK}\" ]; then\r\n DOWNLOAD_LINK=$(echo ${RELEASES} | jq -r --arg VERSION \"${VERSION}\" '.[] | select(.tag_name==$VERSION) | .assets[].browser_download_url' | grep -m 1 -i ${MATCH})\r\n else\r\n echo -e \"defaulting to latest release\"\r\n DOWNLOAD_LINK=$(echo ${LATEST_JSON} | jq .assets | jq -r .[].browser_download_url)\r\n fi\r\nfi\r\n\r\ncurl -L ${DOWNLOAD_LINK} | tar xjv --strip-components=1", + "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n echo \"Config file already exists.\"\r\nelse \r\n echo \"Downloading the config file.\"\r\n apk add --no-cache murmur\r\n cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n apk del murmur\r\nfi\r\necho \"done\"", "container": "ghcr.io\/pterodactyl\/installers:alpine", "entrypoint": "ash" } @@ -35,16 +35,8 @@ "default_value": "100", "user_viewable": true, "user_editable": false, - "rules": "required|numeric|digits_between:1,5" - }, - { - "name": "Server Version", - "description": "Version of Mumble Server to download and use.", - "env_variable": "MUMBLE_VERSION", - "default_value": "latest", - "user_viewable": true, - "user_editable": true, - "rules": "required|string" + "rules": "required|numeric|digits_between:1,5", + "field_type": "text" } ] -} \ No newline at end of file +} diff --git a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json index 2212a3c087..01f7360bcf 100644 --- a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json +++ b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json @@ -1,19 +1,19 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-06-15T17:24:18-04:00", + "exported_at": "2023-01-28T00:52:56+01:00", "name": "Teamspeak3 Server", "author": "support@pterodactyl.io", "description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.", "features": null, - "images": [ - "ghcr.io\/pterodactyl\/yolks:debian" - ], + "docker_images": { + "ghcr.io\/pterodactyl\/yolks:debian": "ghcr.io\/pterodactyl\/yolks:debian" + }, "file_denylist": [], - "startup": ".\/ts3server default_voice_port={{SERVER_PORT}} query_port={{QUERY_PORT}} filetransfer_ip=0.0.0.0 filetransfer_port={{FILE_TRANSFER}} license_accepted=1", + "startup": ".\/ts3server default_voice_port={{SERVER_PORT}} query_port={{QUERY_PORT}} filetransfer_ip=0.0.0.0 filetransfer_port={{FILE_TRANSFER}} query_http_port={{QUERY_HTTP}} query_ssh_port={{QUERY_SSH}} query_protocols={{QUERY_PROTOCOLS_VAR}} serveradmin_password={{SERVERADMIN_PASSWORD}} license_accepted=1", "config": { "files": "{}", "startup": "{\r\n \"done\": \"listening on 0.0.0.0:\"\r\n}", @@ -22,7 +22,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# TS3 Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nif [ -z ${TS_VERSION} ] || [ ${TS_VERSION} == latest ]; then\r\n TS_VERSION=$(curl -sSL https:\/\/teamspeak.com\/versions\/server.json | jq -r '.linux.x86_64.version')\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"getting files from http:\/\/files.teamspeak-services.com\/releases\/server\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\" \r\ncurl -L http:\/\/files.teamspeak-services.com\/releases\/server\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 | tar -xvj --strip-components=1", + "script": "#!\/bin\/ash\r\n# TS3 Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nif [ -z ${TS_VERSION} ] || [ ${TS_VERSION} == latest ]; then\r\n TS_VERSION=$(curl -sSL https:\/\/teamspeak.com\/versions\/server.json | jq -r '.linux.x86_64.version')\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"getting files from http:\/\/files.teamspeak-services.com\/releases\/server\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\" \r\ncurl -L http:\/\/files.teamspeak-services.com\/releases\/server\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 | tar -xvj --strip-components=1\r\ncp .\/redist\/libmariadb.so.2 .\/", "container": "ghcr.io\/pterodactyl\/installers:alpine", "entrypoint": "ash" } @@ -35,7 +35,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:6" + "rules": "required|string|max:6", + "field_type": "text" }, { "name": "File Transfer Port", @@ -44,7 +45,8 @@ "default_value": "30033", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1,65535" + "rules": "required|integer|between:1025,65535", + "field_type": "text" }, { "name": "Query Port", @@ -53,7 +55,48 @@ "default_value": "10011", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1,65535" + "rules": "required|integer|between:1025,65535", + "field_type": "text" + }, + { + "name": "Query Protocols", + "description": "Comma separated list of protocols that can be used to connect to the ServerQuery | \r\nPossible values are raw, ssh and http | \r\nE.g.: raw,ssh,http", + "env_variable": "QUERY_PROTOCOLS_VAR", + "default_value": "raw,http,ssh", + "user_viewable": true, + "user_editable": true, + "rules": "required|string|max:12", + "field_type": "text" + }, + { + "name": "Query SSH Port", + "description": "TCP Port opened for ServerQuery connections using SSH", + "env_variable": "QUERY_SSH", + "default_value": "10022", + "user_viewable": true, + "user_editable": false, + "rules": "required|integer|between:1025,65535", + "field_type": "text" + }, + { + "name": "Query HTTP Port", + "description": "TCP Port opened for ServerQuery connections using http", + "env_variable": "QUERY_HTTP", + "default_value": "10080", + "user_viewable": true, + "user_editable": false, + "rules": "required|integer|between:1025,65535", + "field_type": "text" + }, + { + "name": "Server Query Admin Password", + "description": "The password for the server query admin user.", + "env_variable": "SERVERADMIN_PASSWORD", + "default_value": "", + "user_viewable": true, + "user_editable": true, + "rules": "nullable|string|max:32", + "field_type": "text" } ] } \ No newline at end of file diff --git a/database/migrations/2016_01_23_195641_add_allocations_table.php b/database/migrations/2016_01_23_195641_add_allocations_table.php index cfff2b359e..e6306c3b29 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('allocations'); } diff --git a/database/migrations/2016_01_23_195851_add_api_keys.php b/database/migrations/2016_01_23_195851_add_api_keys.php index af7deb62df..1a7824b1c0 100644 --- a/database/migrations/2016_01_23_195851_add_api_keys.php +++ b/database/migrations/2016_01_23_195851_add_api_keys.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_keys'); } diff --git a/database/migrations/2016_01_23_200044_add_api_permissions.php b/database/migrations/2016_01_23_200044_add_api_permissions.php index e6f6bcbf86..e587da0a3b 100644 --- a/database/migrations/2016_01_23_200044_add_api_permissions.php +++ b/database/migrations/2016_01_23_200044_add_api_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -20,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_permissions'); } diff --git a/database/migrations/2016_01_23_200159_add_downloads.php b/database/migrations/2016_01_23_200159_add_downloads.php index b1771c5e46..9424578fb9 100644 --- a/database/migrations/2016_01_23_200159_add_downloads.php +++ b/database/migrations/2016_01_23_200159_add_downloads.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('downloads'); } diff --git a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php index 83923e7d03..50d42ccc97 100644 --- a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php +++ b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('failed_jobs'); } diff --git a/database/migrations/2016_01_23_200440_create_jobs_table.php b/database/migrations/2016_01_23_200440_create_jobs_table.php index 277acae31b..fe7f9686c0 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -1,5 +1,6 @@ bigIncrements('id'); @@ -19,6 +20,7 @@ public function up() $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); }); } @@ -26,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('jobs'); } diff --git a/database/migrations/2016_01_23_200528_add_locations.php b/database/migrations/2016_01_23_200528_add_locations.php index b34a5fbcce..38d1e1710a 100644 --- a/database/migrations/2016_01_23_200528_add_locations.php +++ b/database/migrations/2016_01_23_200528_add_locations.php @@ -1,5 +1,6 @@ increments('id'); @@ -21,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('locations'); } diff --git a/database/migrations/2016_01_23_200648_add_nodes.php b/database/migrations/2016_01_23_200648_add_nodes.php index 52c0a29e68..371ebf0495 100644 --- a/database/migrations/2016_01_23_200648_add_nodes.php +++ b/database/migrations/2016_01_23_200648_add_nodes.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() $table->mediumInteger('disk_overallocate')->unsigned()->nullable(); $table->char('daemonSecret', 36)->unique(); $table->smallInteger('daemonListen')->unsigned()->default(8080); - $table->smallInteger('daemonSFTP')->unsgined()->default(2022); + $table->smallInteger('daemonSFTP')->unsigned()->default(2022); $table->string('daemonBase')->default('/home/daemon-files'); $table->timestamps(); }); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('nodes'); } diff --git a/database/migrations/2016_01_23_201433_add_password_resets.php b/database/migrations/2016_01_23_201433_add_password_resets.php index 0584e36178..47c49146d8 100644 --- a/database/migrations/2016_01_23_201433_add_password_resets.php +++ b/database/migrations/2016_01_23_201433_add_password_resets.php @@ -1,5 +1,6 @@ string('email')->index(); @@ -20,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('password_resets'); } diff --git a/database/migrations/2016_01_23_201531_add_permissions.php b/database/migrations/2016_01_23_201531_add_permissions.php index 12c9bbe0fa..120a0e0340 100644 --- a/database/migrations/2016_01_23_201531_add_permissions.php +++ b/database/migrations/2016_01_23_201531_add_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('permissions'); } diff --git a/database/migrations/2016_01_23_201649_add_server_variables.php b/database/migrations/2016_01_23_201649_add_server_variables.php index d9a436e6d0..596c619d0c 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('server_variables'); } diff --git a/database/migrations/2016_01_23_201748_add_servers.php b/database/migrations/2016_01_23_201748_add_servers.php index 5e10610691..901c1ff5c2 100644 --- a/database/migrations/2016_01_23_201748_add_servers.php +++ b/database/migrations/2016_01_23_201748_add_servers.php @@ -1,5 +1,6 @@ increments('id'); @@ -39,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('servers'); } diff --git a/database/migrations/2016_01_23_202544_add_service_options.php b/database/migrations/2016_01_23_202544_add_service_options.php index 7b0a336091..382f67a1a5 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_options'); } diff --git a/database/migrations/2016_01_23_202731_add_service_varibles.php b/database/migrations/2016_01_23_202731_add_service_varibles.php index e79fa1fe99..bb96d83b6a 100644 --- a/database/migrations/2016_01_23_202731_add_service_varibles.php +++ b/database/migrations/2016_01_23_202731_add_service_varibles.php @@ -1,5 +1,6 @@ increments('id'); @@ -28,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_variables'); } diff --git a/database/migrations/2016_01_23_202943_add_services.php b/database/migrations/2016_01_23_202943_add_services.php index 31f7234450..caddd964be 100644 --- a/database/migrations/2016_01_23_202943_add_services.php +++ b/database/migrations/2016_01_23_202943_add_services.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('services'); } diff --git a/database/migrations/2016_01_23_203119_create_settings_table.php b/database/migrations/2016_01_23_203119_create_settings_table.php index 2cd6922c2b..40dec55c60 100644 --- a/database/migrations/2016_01_23_203119_create_settings_table.php +++ b/database/migrations/2016_01_23_203119_create_settings_table.php @@ -1,5 +1,6 @@ string('key')->unique(); @@ -19,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('settings'); } diff --git a/database/migrations/2016_01_23_203150_add_subusers.php b/database/migrations/2016_01_23_203150_add_subusers.php index 2f0e463100..e7561c0fd1 100644 --- a/database/migrations/2016_01_23_203150_add_subusers.php +++ b/database/migrations/2016_01_23_203150_add_subusers.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('subusers'); } diff --git a/database/migrations/2016_01_23_203159_add_users.php b/database/migrations/2016_01_23_203159_add_users.php index 05ace7e22e..1b6f9b5bb5 100644 --- a/database/migrations/2016_01_23_203159_add_users.php +++ b/database/migrations/2016_01_23_203159_add_users.php @@ -1,5 +1,6 @@ increments('id'); @@ -27,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('users'); } diff --git a/database/migrations/2016_01_23_203947_create_sessions_table.php b/database/migrations/2016_01_23_203947_create_sessions_table.php index 533fa8aa26..7f708195ea 100644 --- a/database/migrations/2016_01_23_203947_create_sessions_table.php +++ b/database/migrations/2016_01_23_203947_create_sessions_table.php @@ -1,5 +1,6 @@ string('id')->unique(); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('sessions'); } diff --git a/database/migrations/2016_01_25_234418_rename_permissions_column.php b/database/migrations/2016_01_25_234418_rename_permissions_column.php index ae46dceb25..6b75986f93 100644 --- a/database/migrations/2016_01_25_234418_rename_permissions_column.php +++ b/database/migrations/2016_01_25_234418_rename_permissions_column.php @@ -1,5 +1,6 @@ renameColumn('permissions', 'permission'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { }); diff --git a/database/migrations/2016_02_07_172148_add_databases_tables.php b/database/migrations/2016_02_07_172148_add_databases_tables.php index 7b1048b15e..26fdbf3892 100644 --- a/database/migrations/2016_02_07_172148_add_databases_tables.php +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -1,5 +1,6 @@ increments('id'); @@ -25,7 +26,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('databases'); } diff --git a/database/migrations/2016_02_07_181319_add_database_servers_table.php b/database/migrations/2016_02_07_181319_add_database_servers_table.php index 5a6740ae6e..16d2d3cf5a 100644 --- a/database/migrations/2016_02_07_181319_add_database_servers_table.php +++ b/database/migrations/2016_02_07_181319_add_database_servers_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -26,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('database_servers'); } diff --git a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php index c8255ff474..a5d14b6d55 100644 --- a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php +++ b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php @@ -1,5 +1,6 @@ text('executable')->after('docker_image')->nullable()->default(null); @@ -19,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); diff --git a/database/migrations/2016_02_20_155318_add_unique_service_field.php b/database/migrations/2016_02_20_155318_add_unique_service_field.php index 01ff913598..241e278ffb 100644 --- a/database/migrations/2016_02_20_155318_add_unique_service_field.php +++ b/database/migrations/2016_02_20_155318_add_unique_service_field.php @@ -1,5 +1,6 @@ string('file')->unique()->change(); @@ -18,10 +19,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { - $table->dropUnique('services_file_unique'); + $table->dropUnique(['file']); }); } } diff --git a/database/migrations/2016_02_27_163411_add_tasks_table.php b/database/migrations/2016_02_27_163411_add_tasks_table.php index f4cb7b1e36..8fb1efb4aa 100644 --- a/database/migrations/2016_02_27_163411_add_tasks_table.php +++ b/database/migrations/2016_02_27_163411_add_tasks_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks'); } diff --git a/database/migrations/2016_02_27_163447_add_tasks_log_table.php b/database/migrations/2016_02_27_163447_add_tasks_log_table.php index 265e7fd96b..6014a69b81 100644 --- a/database/migrations/2016_02_27_163447_add_tasks_log_table.php +++ b/database/migrations/2016_02_27_163447_add_tasks_log_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks_log'); } diff --git a/database/migrations/2016_08_30_212718_add_ip_alias.php b/database/migrations/2016_08_30_212718_add_ip_alias.php index 26aa5eaa53..17272a2cc6 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -1,5 +1,6 @@ text('ip_alias')->nullable()->after('ip'); @@ -29,7 +30,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('ip_alias'); diff --git a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php index ee7e704fb0..7c8b1d46bf 100644 --- a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php +++ b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php @@ -1,5 +1,6 @@ mediumInteger('allocation')->unsigned()->after('oom_disabled'); @@ -47,7 +48,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->text('ip')->after('allocation'); diff --git a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php index 7bfb75b200..19cd965229 100644 --- a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php +++ b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php @@ -1,5 +1,6 @@ tinyInteger('suspended')->unsigned()->default(0)->after('active'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); diff --git a/database/migrations/2016_09_01_211924_remove_active_column.php b/database/migrations/2016_09_01_211924_remove_active_column.php index 22a2bde136..7450c932d0 100644 --- a/database/migrations/2016_09_01_211924_remove_active_column.php +++ b/database/migrations/2016_09_01_211924_remove_active_column.php @@ -1,5 +1,6 @@ dropColumn('active'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->tinyInteger('active')->after('name')->unsigned()->default(0); diff --git a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php index 565957d599..57ce1f3b58 100644 --- a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php +++ b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php @@ -1,5 +1,6 @@ text('sftp_password')->after('username')->nullable(); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('sftp_password'); diff --git a/database/migrations/2016_09_04_171338_update_jobs_tables.php b/database/migrations/2016_09_04_171338_update_jobs_tables.php index 840ecacb58..4c5bff23fd 100644 --- a/database/migrations/2016_09_04_171338_update_jobs_tables.php +++ b/database/migrations/2016_09_04_171338_update_jobs_tables.php @@ -9,11 +9,12 @@ class UpdateJobsTables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_reserved_at_index'); + $table->dropIndex(['queue', 'reserved', 'reserved_at']); $table->dropColumn('reserved'); + $table->index(['queue', 'reserved_at']); }); } @@ -21,10 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_at_index'); + $table->dropIndex(['queue', 'reserved_at']); + $table->tinyInteger('reserved')->unsigned(); $table->index(['queue', 'reserved', 'reserved_at']); }); diff --git a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php index a00f5f18d7..b5157a1eff 100644 --- a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php +++ b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php @@ -9,7 +9,7 @@ class UpdateFailedJobsTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->text('exception'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->dropColumn('exception'); diff --git a/database/migrations/2016_09_04_182835_create_notifications_table.php b/database/migrations/2016_09_04_182835_create_notifications_table.php index 30fc23a59a..8918f30090 100644 --- a/database/migrations/2016_09_04_182835_create_notifications_table.php +++ b/database/migrations/2016_09_04_182835_create_notifications_table.php @@ -1,5 +1,6 @@ string('id')->primary(); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('notifications'); } diff --git a/database/migrations/2016_09_07_163017_add_unique_identifier.php b/database/migrations/2016_09_07_163017_add_unique_identifier.php index e1bab9ccc0..685a718a46 100644 --- a/database/migrations/2016_09_07_163017_add_unique_identifier.php +++ b/database/migrations/2016_09_07_163017_add_unique_identifier.php @@ -9,7 +9,7 @@ class AddUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->char('author', 36)->after('id'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('author'); diff --git a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php index a7df1ca1b3..8d0ab04ac4 100644 --- a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php +++ b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php @@ -9,7 +9,7 @@ class AllowLongerRegexField extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->text('regex')->change(); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->string('regex')->change(); diff --git a/database/migrations/2016_09_17_194246_add_docker_image_column.php b/database/migrations/2016_09_17_194246_add_docker_image_column.php index 05d26112ec..0e66f649e3 100644 --- a/database/migrations/2016_09_17_194246_add_docker_image_column.php +++ b/database/migrations/2016_09_17_194246_add_docker_image_column.php @@ -1,5 +1,6 @@ string('image')->after('daemonSecret'); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('image'); diff --git a/database/migrations/2016_09_21_165554_update_servers_column_name.php b/database/migrations/2016_09_21_165554_update_servers_column_name.php index 14ae07c4a5..919cdcaab7 100644 --- a/database/migrations/2016_09_21_165554_update_servers_column_name.php +++ b/database/migrations/2016_09_21_165554_update_servers_column_name.php @@ -9,7 +9,7 @@ class UpdateServersColumnName extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server', 'server_id'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server_id', 'server'); diff --git a/database/migrations/2016_09_29_213518_rename_double_insurgency.php b/database/migrations/2016_09_29_213518_rename_double_insurgency.php index adb577754c..5f21c70365 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -7,7 +7,7 @@ class RenameDoubleInsurgency extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $model = DB::table('service_options')->where('parent_service', 2)->where('id', 3)->where('name', 'Insurgency')->first(); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_07_152117_build_api_log_table.php b/database/migrations/2016_10_07_152117_build_api_log_table.php index 08ea312dcc..39356c5bab 100644 --- a/database/migrations/2016_10_07_152117_build_api_log_table.php +++ b/database/migrations/2016_10_07_152117_build_api_log_table.php @@ -9,7 +9,7 @@ class BuildApiLogTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('api_logs', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('api_logs'); } diff --git a/database/migrations/2016_10_14_164802_update_api_keys.php b/database/migrations/2016_10_14_164802_update_api_keys.php index 56c3e80977..b43eef1b1e 100644 --- a/database/migrations/2016_10_14_164802_update_api_keys.php +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -9,7 +9,7 @@ class UpdateApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedInteger('user')->after('id'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('user'); diff --git a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php index 70ec18b33e..a9cf3a35cd 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -7,7 +7,7 @@ class UpdateMisnamedBungee extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('service_variables')->select('env_variable')->where('env_variable', 'BUNGE_VERSION')->update([ 'env_variable' => 'BUNGEE_VERSION', @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php index 1412720c9a..da0fc7c834 100644 --- a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php +++ b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php @@ -9,22 +9,21 @@ class AddForeignKeysServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE servers - MODIFY COLUMN node INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN owner INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN allocation INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN service INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN `option` INT(10) UNSIGNED NOT NULL - '); - Schema::table('servers', function (Blueprint $table) { + $table->integer('node', false, true)->change(); + $table->integer('owner', false, true)->change(); + $table->integer('allocation', false, true)->change(); + $table->integer('service', false, true)->change(); + $table->integer('option', false, true)->change(); + $table->foreign('node')->references('id')->on('nodes'); $table->foreign('owner')->references('id')->on('users'); $table->foreign('allocation')->references('id')->on('allocations'); $table->foreign('service')->references('id')->on('services'); $table->foreign('option')->references('id')->on('service_options'); + $table->softDeletes(); }); } @@ -32,30 +31,31 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); - - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); + $table->dropForeign(['node']); + $table->dropIndex(['node']); + + $table->dropForeign(['owner']); + $table->dropIndex(['owner']); + + $table->dropForeign(['allocation']); + $table->dropIndex(['allocation']); + + $table->dropForeign(['service']); + $table->dropIndex(['service']); + + $table->dropForeign(['option']); + $table->dropIndex(['option']); $table->dropColumn('deleted_at'); - }); - DB::statement('ALTER TABLE servers - MODIFY COLUMN node MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN owner MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN allocation MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN service MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN `option` MEDIUMINT(8) UNSIGNED NOT NULL - '); + $table->mediumInteger('node', false, true)->change(); + $table->mediumInteger('owner', false, true)->change(); + $table->mediumInteger('allocation', false, true)->change(); + $table->mediumInteger('service', false, true)->change(); + $table->mediumInteger('option', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_201624_add_foreign_allocations.php b/database/migrations/2016_10_23_201624_add_foreign_allocations.php index 0660081cb7..7ae4b040dd 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -1,5 +1,6 @@ integer('assigned_to', false, true)->nullable()->change(); + $table->integer('node', false, true)->nullable(false)->change(); $table->foreign('assigned_to')->references('id')->on('servers'); $table->foreign('node')->references('id')->on('nodes'); }); @@ -25,14 +23,17 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropForeign('allocations_node_foreign'); + $table->dropForeign(['assigned_to']); + $table->dropIndex(['assigned_to']); + + $table->dropForeign(['node']); + $table->dropIndex(['node']); - $table->dropIndex('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); + $table->mediumInteger('assigned_to', false, true)->nullable()->change(); + $table->mediumInteger('node', false, true)->nullable(false)->change(); }); DB::statement('ALTER TABLE allocations diff --git a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php index 700342d749..44b11d0e5a 100644 --- a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php +++ b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php @@ -9,7 +9,7 @@ class AddForeignApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->foreign('user')->references('id')->on('users'); @@ -19,11 +19,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign'); - $table->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); + $table->dropIndex(['user']); }); } } diff --git a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php index d8eb3504da..2494eaba7d 100644 --- a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php +++ b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php @@ -9,11 +9,10 @@ class AddForeignApiPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE api_permissions MODIFY key_id INT(10) UNSIGNED NOT NULL'); - Schema::table('api_permissions', function (Blueprint $table) { + $table->integer('key_id', false, true)->nullable(false)->change(); $table->foreign('key_id')->references('id')->on('api_keys'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { - $table->dropForeign('api_permissions_key_id_foreign'); - $table->dropIndex('api_permissions_key_id_foreign'); - }); + $table->dropForeign(['key_id']); + $table->dropIndex(['key_id']); - DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('key_id', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php index 769b7daa3d..78ee8264db 100644 --- a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php +++ b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php @@ -9,7 +9,7 @@ class AddForeignDatabaseServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->foreign('linked_node')->references('id')->on('nodes'); @@ -19,11 +19,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_servers', function (Blueprint $table) { - $table->dropForeign('database_servers_linked_node_foreign'); - $table->dropIndex('database_servers_linked_node_foreign'); + $table->dropForeign(['linked_node']); + $table->dropIndex(['linked_node']); }); } } diff --git a/database/migrations/2016_10_23_203105_add_foreign_databases.php b/database/migrations/2016_10_23_203105_add_foreign_databases.php index be26e3cb01..bea43049bf 100644 --- a/database/migrations/2016_10_23_203105_add_foreign_databases.php +++ b/database/migrations/2016_10_23_203105_add_foreign_databases.php @@ -9,7 +9,7 @@ class AddForeignDatabases extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->foreign('server_id')->references('id')->on('servers'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { - $table->dropForeign('databases_server_id_foreign'); - $table->dropForeign('databases_db_server_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); - $table->dropIndex('databases_server_id_foreign'); - $table->dropIndex('databases_db_server_foreign'); + $table->dropForeign(['db_server']); + $table->dropIndex(['db_server']); }); } } diff --git a/database/migrations/2016_10_23_203335_add_foreign_nodes.php b/database/migrations/2016_10_23_203335_add_foreign_nodes.php index f861e0a7d2..375189a7fd 100644 --- a/database/migrations/2016_10_23_203335_add_foreign_nodes.php +++ b/database/migrations/2016_10_23_203335_add_foreign_nodes.php @@ -9,11 +9,10 @@ class AddForeignNodes extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE nodes MODIFY location INT(10) UNSIGNED NOT NULL'); - Schema::table('nodes', function (Blueprint $table) { + $table->integer('location', false, true)->nullable(false)->change(); $table->foreign('location')->references('id')->on('locations'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); - }); + $table->dropForeign(['location']); + $table->dropIndex(['location']); - DB::statement('ALTER TABLE nodes MODIFY location MEDIUMINT(10) UNSIGNED NOT NULL'); + $table->mediumInteger('location', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_203522_add_foreign_permissions.php b/database/migrations/2016_10_23_203522_add_foreign_permissions.php index a43f0eacf3..78bbf32a5e 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -9,7 +9,7 @@ class AddForeignPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_user_id_foreign'); - $table->dropForeign('permissions_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('permissions_user_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php index b4720495d4..3ccc3d1835 100644 --- a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php +++ b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php @@ -9,14 +9,11 @@ class AddForeignServerVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id INT(10) UNSIGNED NULL, - MODIFY COLUMN variable_id INT(10) UNSIGNED NOT NULL - '); - Schema::table('server_variables', function (Blueprint $table) { + $table->integer('server_id', false, true)->nullable()->change(); + $table->integer('variable_id', false, true)->nullable(false)->change(); $table->foreign('server_id')->references('id')->on('servers'); $table->foreign('variable_id')->references('id')->on('service_variables'); }); @@ -25,16 +22,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); $table->dropForeign(['variable_id']); + $table->mediumInteger('server_id', false, true)->nullable()->change(); + $table->mediumInteger('variable_id', false, true)->nullable(false)->change(); }); - - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id MEDIUMINT(8) UNSIGNED NULL, - MODIFY COLUMN variable_id MEDIUMINT(8) UNSIGNED NOT NULL - '); } } diff --git a/database/migrations/2016_10_23_204157_add_foreign_service_options.php b/database/migrations/2016_10_23_204157_add_foreign_service_options.php index cb8c0e2e85..9f01905b7f 100644 --- a/database/migrations/2016_10_23_204157_add_foreign_service_options.php +++ b/database/migrations/2016_10_23_204157_add_foreign_service_options.php @@ -9,11 +9,10 @@ class AddForeignServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_options MODIFY parent_service INT(10) UNSIGNED NOT NULL'); - Schema::table('service_options', function (Blueprint $table) { + $table->integer('parent_service', false, true)->change(); $table->foreign('parent_service')->references('id')->on('services'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); - }); + $table->dropForeign(['parent_service']); + $table->dropIndex(['parent_service']); - DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('parent_service', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php index 02bbc46f28..df998efafa 100644 --- a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php +++ b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php @@ -9,11 +9,10 @@ class AddForeignServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_variables MODIFY option_id INT(10) UNSIGNED NOT NULL'); - Schema::table('service_variables', function (Blueprint $table) { + $table->integer('option_id', false, true)->change(); $table->foreign('option_id')->references('id')->on('service_options'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { - $table->dropForeign('service_variables_option_id_foreign'); - $table->dropIndex('service_variables_option_id_foreign'); - }); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); - DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('option_id', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204454_add_foreign_subusers.php b/database/migrations/2016_10_23_204454_add_foreign_subusers.php index b637c80ae5..ff4bb95a3e 100644 --- a/database/migrations/2016_10_23_204454_add_foreign_subusers.php +++ b/database/migrations/2016_10_23_204454_add_foreign_subusers.php @@ -9,7 +9,7 @@ class AddForeignSubusers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { - $table->dropForeign('subusers_user_id_foreign'); - $table->dropForeign('subusers_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('subusers_user_id_foreign'); - $table->dropIndex('subusers_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_204610_add_foreign_tasks.php b/database/migrations/2016_10_23_204610_add_foreign_tasks.php index 18ea297e54..f32d89230e 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -9,7 +9,7 @@ class AddForeignTasks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->foreign('server')->references('id')->on('servers'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); diff --git a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php index 1547e32cc1..c5fff5523f 100644 --- a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php +++ b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php @@ -7,7 +7,7 @@ class AddArkServiceOptionFixed extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); @@ -73,7 +73,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php index b6fa0972ba..8fd638ae6c 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -9,7 +9,7 @@ class AddPackSupport extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('service_packs', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('service_packs'); } diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php index 42b0f6953e..261fdb3563 100644 --- a/database/migrations/2016_11_11_231731_set_service_name_unique.php +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -9,7 +9,7 @@ class SetServiceNameUnique extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->unique('name'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique('services_name_unique'); diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php index d520466a88..3911ecb416 100644 --- a/database/migrations/2016_11_27_142519_add_pack_column.php +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -9,7 +9,7 @@ class AddPackColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack')->nullable()->after('option'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack']); diff --git a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php index d2d14f4d00..c5136fe9e4 100644 --- a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php +++ b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php @@ -9,7 +9,7 @@ class AddConfigurableUploadLimit extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedInteger('upload_size')->after('disk_overallocate')->default(100); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('upload_size'); diff --git a/database/migrations/2016_12_02_185206_correct_service_variables.php b/database/migrations/2016_12_02_185206_correct_service_variables.php index e9c87989ac..d94b3b78bd 100644 --- a/database/migrations/2016_12_02_185206_correct_service_variables.php +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -7,7 +7,7 @@ class CorrectServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { // Modify Default Spigot Startup Line @@ -66,7 +66,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { // do nothing } diff --git a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php index 7cdf96807c..35248d6bbd 100644 --- a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php +++ b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php @@ -7,7 +7,7 @@ class FixMisnamedOptionTag extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { DB::table('service_options')->where([ @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::table('service_options')->where([ ['name', 'Sponge (SpongeVanilla)'], diff --git a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php index 77693c265a..c4369f975b 100644 --- a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php +++ b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php @@ -9,7 +9,7 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); @@ -24,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('node_configuration_tokens'); } diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php index 0206040b50..82ae8c9e97 100644 --- a/database/migrations/2017_01_12_135449_add_more_user_data.php +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -10,7 +10,7 @@ class AddMoreUserData extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('name_first')->after('email')->nullable(); @@ -34,7 +34,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('name_first'); diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php index c88aa8de73..7195133130 100644 --- a/database/migrations/2017_02_02_175548_UpdateColumnNames.php +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -9,22 +9,15 @@ class UpdateColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); - $table->dropForeign('servers_pack_foreign'); - - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); - $table->dropIndex('servers_pack_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['owner']); + $table->dropForeign(['allocation']); + $table->dropForeign(['service']); + $table->dropForeign(['option']); + $table->dropForeign(['pack']); $table->renameColumn('node', 'node_id'); $table->renameColumn('owner', 'owner_id'); @@ -47,14 +40,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign(['node_id']); - $table->dropForeign(['owner_id']); - $table->dropForeign(['allocation_id']); - $table->dropForeign(['service_id']); - $table->dropForeign(['option_id']); + $table->dropForeign(['node_id', 'owner_id', 'allocation_id', 'service_id', 'option_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('owner_id', 'owner'); diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php index 58ec63ef4a..e797cc704f 100644 --- a/database/migrations/2017_02_03_140948_UpdateNodesTable.php +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -9,11 +9,10 @@ class UpdateNodesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); + $table->dropForeign(['location']); $table->renameColumn('location', 'location_id'); $table->foreign('location_id')->references('id')->on('locations'); @@ -23,11 +22,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_id_foreign'); - $table->dropIndex('nodes_location_id_foreign'); + $table->dropForeign(['location_id']); $table->renameColumn('location_id', 'location'); $table->foreign('location')->references('id')->on('locations'); diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php index 5f617abec1..bd50e16bec 100644 --- a/database/migrations/2017_02_03_155554_RenameColumns.php +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -9,13 +9,11 @@ class RenameColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_foreign'); - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); - $table->dropIndex('allocations_assigned_to_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['assigned_to']); $table->renameColumn('node', 'node_id'); $table->renameColumn('assigned_to', 'server_id'); @@ -27,13 +25,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_id_foreign'); - $table->dropForeign('allocations_server_id_foreign'); - $table->dropIndex('allocations_node_id_foreign'); - $table->dropIndex('allocations_server_id_foreign'); + $table->dropForeign(['node_id']); + $table->dropForeign(['server_id']); + $table->dropIndex(['node_id']); + $table->dropIndex(['server_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('server_id', 'assigned_to'); diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php index c7688f0564..51c8818c7b 100644 --- a/database/migrations/2017_02_05_164123_AdjustColumnNames.php +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -9,11 +9,10 @@ class AdjustColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); + $table->dropForeign(['parent_service']); $table->renameColumn('parent_service', 'service_id'); $table->foreign('service_id')->references('id')->on('services'); @@ -23,11 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_service_id_foreign'); - $table->dropIndex('service_options_service_id_foreign'); + $table->dropForeign(['service_id']); + $table->dropIndex(['service_id']); $table->renameColumn('service_id', 'parent_service'); $table->foreign('parent_service')->references('id')->on('services'); diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php index 6f86b3b6e7..69dc33dda2 100644 --- a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -9,11 +9,10 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_foreign'); - $table->dropIndex('service_packs_option_foreign'); + $table->dropForeign(['option']); $table->renameColumn('option', 'option_id'); $table->foreign('option_id')->references('id')->on('service_options'); @@ -23,11 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_id_foreign'); - $table->dropIndex('service_packs_option_id_foreign'); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); $table->renameColumn('option_id', 'option'); $table->foreign('option')->references('id')->on('service_options'); diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php index 45efce83a8..bf6469506b 100644 --- a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -11,7 +11,7 @@ class SetupPermissionsPivotTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('subuser_id')->after('id'); @@ -19,17 +19,15 @@ public function up() DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ + Permission::query()->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ 'subuser_id' => $subuser->id, ]); } }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_server_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); - $table->dropForeign('permissions_user_id_foreign'); - $table->dropIndex('permissions_user_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropForeign(['user_id']); $table->dropColumn('server_id'); $table->dropColumn('user_id'); @@ -42,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('server_id')->after('subuser_id'); @@ -52,7 +50,7 @@ public function down() DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('subuser_id', $subuser->id)->update([ + Permission::query()->where('subuser_id', $subuser->id)->update([ 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, ]); @@ -60,8 +58,8 @@ public function down() }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_subuser_id_foreign'); - $table->dropIndex('permissions_subuser_id_foreign'); + $table->dropForeign(['subuser_id']); + $table->dropIndex(['subuser_id']); $table->dropColumn('subuser_id'); $table->foreign('server_id')->references('id')->on('servers'); diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php index 8b541d9415..8ae28c2c95 100644 --- a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -9,10 +9,10 @@ class UpdateAPIKeyColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign')->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); $table->renameColumn('user', 'user_id'); $table->foreign('user_id')->references('id')->on('users'); @@ -22,10 +22,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_id_foreign')->dropIndex('api_keys_user_id_foreign'); + $table->dropForeign(['user_id']); $table->renameColumn('user_id', 'user'); $table->foreign('user')->references('id')->on('users'); diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php index 4f27346fa4..aab6c2b95a 100644 --- a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -9,7 +9,7 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index 6792f265a6..d697a3315f 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -9,7 +9,7 @@ class DeleteServiceExecutableOption extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->renameColumn('file', 'folder'); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->string('executable')->after('folder'); diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php index 385004fa46..06c04694cd 100644 --- a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -9,7 +9,7 @@ class AddNewServiceOptionsColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); @@ -27,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['config_from']); diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index c786ab4688..40aef15246 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -1,11 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ + use Illuminate\Database\Migrations\Migration; class MigrateToNewServiceSystem extends Migration @@ -13,7 +7,7 @@ class MigrateToNewServiceSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); @@ -38,7 +32,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { // Not doing reversals right now... } diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 21fa514651..3e7e5f18b8 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -9,7 +9,7 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('regex', 'rules'); @@ -30,7 +30,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('rules', 'regex'); diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index 3628ba7a4b..26599246cd 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -85,7 +85,7 @@ class Service extends Core { /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->text('index_file')->after('startup'); @@ -105,7 +105,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('index_file'); diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php index d01012e41c..f73befdba6 100644 --- a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -9,7 +9,7 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -25,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php index b1a8ee3a04..b396954e06 100644 --- a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -9,7 +9,7 @@ class AddLockedStatusToTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->boolean('locked')->default(false)->after('visible'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropColumn('locked'); diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php index a7166df9e0..c973faa555 100644 --- a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -9,7 +9,7 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->dropForeign(['linked_node']); @@ -27,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php index bc6fb45c77..2b689c4817 100644 --- a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -9,7 +9,7 @@ class CleanupDatabasesDatabase extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['db_server']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php index 3f26a1e34a..bdd1f1a543 100644 --- a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -9,7 +9,7 @@ class AddForeignKeyToPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->foreign('pack_id')->references('id')->on('packs'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php index e8ebcb20dc..69d0445823 100644 --- a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -9,7 +9,7 @@ class AddServerDescriptionColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->text('description')->after('name'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php index 3cd08f1a94..0c193192b2 100644 --- a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -9,7 +9,7 @@ class DropDeletedAtColumnFromServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('deleted_at'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->timestamp('deleted_at')->nullable(); diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php index d069e1ba18..68d9492e30 100644 --- a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -10,7 +10,7 @@ class UpgradeTaskSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); @@ -33,11 +33,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { -// $table->dropForeign(['server_id']); -// $table->dropForeign(['user_id']); + // $table->dropForeign(['server_id']); + // $table->dropForeign(['user_id']); $table->renameColumn('server_id', 'server'); $table->dropColumn('user_id'); diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php index ba2f57c41e..96a85be929 100644 --- a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -9,7 +9,7 @@ class AddScriptsToServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->text('script_install')->after('startup')->nullable(); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('script_install'); diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php index 2bc8f27b31..970b417730 100644 --- a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -9,7 +9,7 @@ class AddServiceScriptTrackingToServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->boolean('skip_scripts')->default(false)->after('description'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('skip_scripts'); diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php index 514d17e1c0..8888600fba 100644 --- a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -9,7 +9,7 @@ class AddCopyScriptFromColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->unsignedInteger('copy_script_from')->nullable()->after('script_container'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['copy_script_from']); diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php index aa5e04498b..96bb9aec55 100644 --- a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -9,7 +9,7 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('behind_proxy')->after('scheme')->default(false); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('behind_proxy'); diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php index 7dcae3c6f5..967c12615a 100644 --- a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -9,7 +9,7 @@ class DeleteDownloadTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('downloads'); } @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('downloads', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php index 90c8c4b1ee..d230bc19ac 100644 --- a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -9,7 +9,7 @@ class DeleteNodeConfigurationTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('node_configuration_tokens'); } @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_06_10_152951_add_external_id_to_users.php b/database/migrations/2017_06_10_152951_add_external_id_to_users.php index 9ce5057e83..bccfb43fde 100644 --- a/database/migrations/2017_06_10_152951_add_external_id_to_users.php +++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php @@ -9,7 +9,7 @@ class AddExternalIdToUsers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->after('id')->nullable()->unique(); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php index a089ab4db3..6f36d0e050 100644 --- a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php +++ b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php @@ -9,7 +9,7 @@ class ChangeForeignKeyToBeOnCascadeDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); diff --git a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php index 0bfc7d5270..10058c8ccf 100644 --- a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php +++ b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php @@ -9,7 +9,7 @@ class ChangeUserPermissionsToDeleteOnUserDeletion extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->dropForeign(['subuser_id']); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->dropForeign(['user_id']); diff --git a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php index fb156ba8c1..8ac6eccec5 100644 --- a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php +++ b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php @@ -9,7 +9,7 @@ class SetAllocationToReferenceNullOnServerDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php index 5ae9a29f95..ca5a4623f1 100644 --- a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php +++ b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAServerOrVariableIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php index 89e1102289..cf0a4bba17 100644 --- a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -9,7 +9,7 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php index a33b78af6c..0eabe77db5 100644 --- a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php +++ b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class CascadeNullValuesForDatabaseHostWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -20,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php index 77b7f984c9..3fb457dc47 100644 --- a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php +++ b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php @@ -9,7 +9,7 @@ class AllowNegativeValuesForOverallocation extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->integer('disk_overallocate')->default(0)->nullable(false)->change(); @@ -20,10 +20,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, + DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, MODIFY memory_overallocate MEDIUMINT UNSIGNED NULL'); }); } diff --git a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php index f7aab7c047..f2d8ad9bf8 100644 --- a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php +++ b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php @@ -9,7 +9,7 @@ class SetAllocationUnqiueUsingMultipleFields extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->unique(['node_id', 'ip', 'port']); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php index 074f872e0d..fbea750bc6 100644 --- a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php +++ b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAParentServiceIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); diff --git a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php index 1b8f1a5677..7c59d801e0 100644 --- a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php +++ b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class RemovePackWhenParentServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php index 12eada73c8..14f60b3b65 100644 --- a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php +++ b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php @@ -8,7 +8,7 @@ class RenameTasksTableForStructureRefactor extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::rename('tasks', 'tasks_old'); } @@ -16,7 +16,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::rename('tasks_old', 'tasks'); } diff --git a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php index 3d5baa6d3e..588f48c8f5 100644 --- a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php +++ b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php @@ -9,7 +9,7 @@ class CreateSchedulesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('schedules', function (Blueprint $table) { $table->increments('id'); @@ -32,7 +32,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('schedules'); } diff --git a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php index 9c225a834f..969c153612 100644 --- a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php +++ b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php @@ -9,7 +9,7 @@ class CreateNewTasksTableForSchedules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('tasks'); } diff --git a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php index 2a20ef10e7..4656e272e8 100644 --- a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php +++ b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php @@ -2,6 +2,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; @@ -10,40 +11,40 @@ class TransferOldTasksToNewScheduler extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - $tasks = DB::table('tasks_old')->get(); + DB::transaction(function () { + $tasks = DB::table('tasks_old')->get(); - DB::beginTransaction(); - $tasks->each(function ($task) { - $schedule = DB::table('schedules')->insertGetId([ - 'server_id' => $task->server_id, - 'name' => null, - 'cron_day_of_week' => $task->day_of_week, - 'cron_day_of_month' => $task->day_of_month, - 'cron_hour' => $task->hour, - 'cron_minute' => $task->minute, - 'is_active' => (bool) $task->active, - 'is_processing' => false, - 'last_run_at' => $task->last_run, - 'next_run_at' => $task->next_run, - 'created_at' => $task->created_at, - 'updated_at' => Carbon::now()->toDateTimeString(), - ]); + $tasks->each(function ($task) { + $schedule = DB::table('schedules')->insertGetId([ + 'server_id' => $task->server_id, + 'name' => null, + 'cron_day_of_week' => $task->day_of_week, + 'cron_day_of_month' => $task->day_of_month, + 'cron_hour' => $task->hour, + 'cron_minute' => $task->minute, + 'is_active' => (bool) $task->active, + 'is_processing' => false, + 'last_run_at' => $task->last_run, + 'next_run_at' => $task->next_run, + 'created_at' => $task->created_at, + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks')->insert([ - 'schedule_id' => $schedule, - 'sequence_id' => 1, - 'action' => $task->action, - 'payload' => $task->data, - 'time_offset' => 0, - 'is_queued' => false, - 'updated_at' => Carbon::now()->toDateTimeString(), - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + DB::table('tasks')->insert([ + 'schedule_id' => $schedule, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->data, + 'time_offset' => 0, + 'is_queued' => false, + 'updated_at' => Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks_old')->delete($task->id); - DB::commit(); + DB::table('tasks_old')->delete($task->id); + }); }); Schema::dropIfExists('tasks_old'); @@ -52,7 +53,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('tasks_old', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php index ba3a8bac0e..7c0c574470 100644 --- a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php +++ b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php @@ -8,7 +8,7 @@ class UpdateOldPermissionsToPointToNewScheduleSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-task%')->get(); foreach ($permissions as $record) { @@ -26,7 +26,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-schedule%')->get(); foreach ($permissions as $record) { diff --git a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php index cfbfc88b04..64ff02666a 100644 --- a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php +++ b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php @@ -9,7 +9,7 @@ class CreateDaemonKeysTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('daemon_keys'); } diff --git a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php index 84cb2d92bb..b284905a0f 100644 --- a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php +++ b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php @@ -12,7 +12,7 @@ class RemoveDaemonSecretFromServersTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $inserts = []; @@ -41,7 +41,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('startup')->unique(); diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php index d4d2dd695e..9ea90cff28 100644 --- a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -1,6 +1,7 @@ get(); @@ -39,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('server_id'); diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php index dffa7687aa..aae62921a3 100644 --- a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -11,7 +11,7 @@ class ChangeServicesToUseAMoreUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique(['name']); @@ -39,7 +39,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php index 5c9df79a5e..679a8b5e02 100644 --- a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php +++ b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php @@ -11,7 +11,7 @@ class ChangeToABetterUniqueServiceConfiguration extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); @@ -40,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php index 3b19e3d998..9a64abf074 100644 --- a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php +++ b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php index e7b70136ce..cea7460e63 100644 --- a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php +++ b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php @@ -1,5 +1,6 @@ dropUnique(['username']); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->string('username')->nullable()->after('image')->unique(); diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php index 53cb6526b1..b02e326c64 100644 --- a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -12,7 +12,7 @@ class Add2FaLastAuthorizationTimeColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->text('totp_secret')->nullable()->change(); @@ -27,7 +27,7 @@ public function up() DB::table('users')->where('id', $user->id)->update([ 'totp_secret' => Crypt::encrypt($user->totp_secret), - 'updated_at' => Carbon::now()->toIso8601String(), + 'updated_at' => Carbon::now()->toAtomString(), ]); }); }); @@ -36,7 +36,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { DB::table('users')->get()->each(function ($user) { @@ -46,7 +46,7 @@ public function down() DB::table('users')->where('id', $user->id)->update([ 'totp_secret' => Crypt::decrypt($user->totp_secret), - 'updated_at' => Carbon::now()->toIso8601String(), + 'updated_at' => Carbon::now()->toAtomString(), ]); }); }); diff --git a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php index c2947ee07c..229827dbcc 100644 --- a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php +++ b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php @@ -1,5 +1,6 @@ get()->each(function ($item) { try { $decrypted = Crypt::decrypt($item->secret); - } catch (DecryptException $exception) { + } catch (DecryptException) { $decrypted = str_random(32); } finally { DB::table('api_keys')->where('id', $item->id)->update([ @@ -30,27 +31,41 @@ public function up() Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('public'); - $table->string('secret', 32)->change(); + $table->renameColumn('secret', 'token'); }); - DB::statement('ALTER TABLE `api_keys` CHANGE `secret` `token` CHAR(32) NOT NULL, ADD UNIQUE INDEX `api_keys_token_unique` (`token`(32))'); + Schema::table('api_keys', function (Blueprint $table) { + $table->char('token', 32)->change(); + $table->unique('token'); + }); } /** * Reverse the migrations. */ - public function down() + public function down(): void { - DB::statement('ALTER TABLE `api_keys` CHANGE `token` `secret` TEXT, DROP INDEX `api_keys_token_unique`'); + Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique(['token']); + $table->renameColumn('token', 'secret'); + }); Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique('token'); + $table->text('token')->change(); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->renameColumn('token', 'secret'); + + $table->text('secret')->nullable()->change(); $table->char('public', 16)->after('user_id'); }); DB::transaction(function () { DB::table('api_keys')->get()->each(function ($item) { DB::table('api_keys')->where('id', $item->id)->update([ - 'public' => str_random(16), + 'public' => Str::random(16), 'secret' => Crypt::encrypt($item->secret), ]); }); diff --git a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php index d28109598c..9a0cc9114f 100644 --- a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php +++ b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class DropAllocationsWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php index 1bdaf6477b..8d639a37a9 100644 --- a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php +++ b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php @@ -10,7 +10,7 @@ class MigrateSettingsTableToNewFormat extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('settings')->truncate(); Schema::table('settings', function (Blueprint $table) { @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('settings', function (Blueprint $table) { $table->dropColumn('id'); diff --git a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php index 8f9938da15..7ccae5d61a 100644 --- a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php +++ b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php @@ -8,10 +8,8 @@ class AllowNegativeValuesForServerSwap extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->integer('swap')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('swap')->change(); diff --git a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php index adc6d2648f..118a422f4a 100644 --- a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php +++ b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php @@ -8,10 +8,8 @@ class AddApiKeyPermissionColumns extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('api_permissions'); @@ -31,10 +29,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('api_permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php index 1d36b3648b..f45b5bd85f 100644 --- a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php +++ b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php @@ -9,12 +9,10 @@ class SetupTableForKeyEncryption extends Migration /** * Run the migrations. * - * @return void - * - * @throws \Exception - * @throws \Throwable + * @throws Exception + * @throws Throwable */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->char('identifier', 16)->nullable()->unique()->after('user_id'); @@ -29,12 +27,10 @@ public function up() /** * Reverse the migrations. * - * @return void - * - * @throws \Exception - * @throws \Throwable + * @throws Exception + * @throws Throwable */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('identifier'); diff --git a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php index e0f86b9de8..f78f7a5d16 100644 --- a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php +++ b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php @@ -8,10 +8,8 @@ class AddLastUsedAtColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('key_type')->after('user_id')->default(0); @@ -28,10 +26,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->timestamp('expires_at')->after('memo')->nullable(); diff --git a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php index 6a4a04e7d8..6166f016e5 100644 --- a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php +++ b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php @@ -8,10 +8,8 @@ class AllowTextInUserExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->nullable()->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->change(); diff --git a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php index b587cdcb06..64dbaf0dca 100644 --- a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php +++ b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php @@ -8,10 +8,8 @@ class RemoveUniqueIndexOnExternalIdColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->dropUnique(['external_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unique(['external_id']); diff --git a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php index bff7bbfb06..99f1db4549 100644 --- a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php +++ b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php @@ -8,10 +8,8 @@ class EnsureUniqueAllocationIdOnServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unique(['allocation_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['allocation_id']); diff --git a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php index 2c8af99e22..c7d0f8fd45 100644 --- a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php +++ b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddExternalIdColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('external_id')->after('id')->nullable()->unique(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php index 6469867f25..2db2f375ee 100644 --- a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php +++ b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php @@ -10,10 +10,10 @@ class RemoveDefaultNullValueOnTable extends Migration /** * Run the migrations. * - * @throws \Exception - * @throws \Throwable + * @throws Exception + * @throws Throwable */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->default(null)->change(); @@ -28,10 +28,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // This should not be rolled back. } diff --git a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php index 0a9b8afe2b..38469af237 100644 --- a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php +++ b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php @@ -8,10 +8,8 @@ class DefineUniqueIndexOnUsersExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->index(['external_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropIndex(['external_id']); diff --git a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php index 4e85e8aebb..00fbd11c2b 100644 --- a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php +++ b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php @@ -8,10 +8,8 @@ class AddDatabaseAndPortLimitColumnsToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('database_limit')->after('installed')->nullable()->default(0); @@ -21,10 +19,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn(['database_limit', 'allocation_limit']); diff --git a/database/migrations/2018_03_15_124536_add_description_to_nodes.php b/database/migrations/2018_03_15_124536_add_description_to_nodes.php index 7208a4207c..a5c1b75421 100644 --- a/database/migrations/2018_03_15_124536_add_description_to_nodes.php +++ b/database/migrations/2018_03_15_124536_add_description_to_nodes.php @@ -8,10 +8,8 @@ class AddDescriptionToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->text('description')->after('name'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php index 04fdf000f3..e85eca8cda 100644 --- a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php +++ b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php @@ -8,10 +8,8 @@ class AddMaintenanceToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('maintenance_mode')->after('behind_proxy')->default(false); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('maintenance_mode'); diff --git a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php index 1996509407..e7a4089fa0 100644 --- a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowEggVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->text('default_value')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->string('default_value')->change(); diff --git a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php index cc90e0e068..a1d581819b 100644 --- a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowServerVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->text('variable_value')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->string('variable_value')->change(); diff --git a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php index d91ce63720..6d43197dea 100644 --- a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php +++ b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php @@ -8,10 +8,8 @@ class SetAllocationLimitDefaultNull extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(0)->change(); diff --git a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php index 59425aee76..de110a06bc 100644 --- a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php +++ b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php @@ -8,10 +8,8 @@ class FixUniqueIndexToAccountForHost extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database']); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php index 27d26674fa..b71189fe02 100644 --- a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -61,10 +61,8 @@ class MergePermissionsTableIntoSubusers extends Migration /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->json('permissions')->nullable()->after('server_id'); @@ -72,7 +70,12 @@ public function up() $cursor = DB::table('permissions') ->select(['subuser_id']) - ->selectRaw('GROUP_CONCAT(permission) as permissions') + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql', function ($query) { + $query->selectRaw('group_concat(permission) as permissions'); + }) + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'pgsql', function ($query) { + $query->selectRaw("string_agg(permission, ',') as permissions"); + }) ->from('permissions') ->groupBy(['subuser_id']) ->cursor(); @@ -98,10 +101,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { $flipped = array_flip(array_filter(self::$permissionsMap)); diff --git a/database/migrations/2020_03_22_164814_drop_permissions_table.php b/database/migrations/2020_03_22_164814_drop_permissions_table.php index da9d677a8d..030a8a6bad 100644 --- a/database/migrations/2020_03_22_164814_drop_permissions_table.php +++ b/database/migrations/2020_03_22_164814_drop_permissions_table.php @@ -8,20 +8,16 @@ class DropPermissionsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('permissions'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php index 9b0202cab2..d4c08c5e5f 100644 --- a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php +++ b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddThreadsColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('threads')->nullable()->after('cpu'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('threads'); diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php index daa35dd3b5..a8c28d96d5 100644 --- a/database/migrations/2020_04_03_230614_create_backups_table.php +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -9,10 +9,8 @@ class CreateBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // There exists a backups plugin for the 0.7 version of the Panel. However, it didn't properly @@ -49,10 +47,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('backups'); } diff --git a/database/migrations/2020_04_04_131016_add_table_server_transfers.php b/database/migrations/2020_04_04_131016_add_table_server_transfers.php index 096b5384f4..c9f3e849a9 100644 --- a/database/migrations/2020_04_04_131016_add_table_server_transfers.php +++ b/database/migrations/2020_04_04_131016_add_table_server_transfers.php @@ -8,10 +8,8 @@ class AddTableServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Nuclear approach to whatever plugins are out there and not properly namespacing their own tables // leading to constant support requests from people... @@ -20,13 +18,13 @@ public function up() Schema::create('server_transfers', function (Blueprint $table) { $table->increments('id'); $table->integer('server_id')->unsigned(); - $table->tinyInteger('successful')->unsigned()->default(0); + $table->boolean('successful')->unsigned()->default(0); $table->integer('old_node')->unsigned(); $table->integer('new_node')->unsigned(); $table->integer('old_allocation')->unsigned(); $table->integer('new_allocation')->unsigned(); - $table->string('old_additional_allocations')->nullable(); - $table->string('new_additional_allocations')->nullable(); + $table->json('old_additional_allocations')->nullable(); + $table->json('new_additional_allocations')->nullable(); $table->timestamps(); $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); @@ -35,10 +33,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('server_transfers'); } diff --git a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php index 6544679fe8..2e750dfa21 100644 --- a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php +++ b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php @@ -13,11 +13,9 @@ class StoreNodeTokensAsEncryptedValue extends Migration /** * Run the migrations. * - * @return void - * - * @throws \Exception + * @throws Exception */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropUnique(['daemonSecret']); @@ -26,14 +24,15 @@ public function up() Schema::table('nodes', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); $table->char('daemon_token_id', 16)->after('upload_size'); - $table->renameColumn('daemonSecret', 'daemon_token'); + + $table->renameColumn('`daemonSecret`', 'daemon_token'); }); Schema::table('nodes', function (Blueprint $table) { $table->text('daemon_token')->change(); }); - /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ + /** @var Encrypter $encrypter */ $encrypter = Container::getInstance()->make(Encrypter::class); foreach (DB::select('SELECT id, daemon_token FROM nodes') as $datum) { @@ -53,13 +52,11 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { DB::transaction(function () { - /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ + /** @var Encrypter $encrypter */ $encrypter = Container::getInstance()->make(Encrypter::class); foreach (DB::select('SELECT id, daemon_token_id, daemon_token FROM nodes') as $datum) { diff --git a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php index dfd55fb423..8c2c149cfd 100644 --- a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php +++ b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php @@ -8,10 +8,8 @@ class AllowNullableDescriptions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable()->change(); @@ -32,10 +30,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable(false)->change(); diff --git a/database/migrations/2020_04_22_055500_add_max_connections_column.php b/database/migrations/2020_04_22_055500_add_max_connections_column.php index 02253dfd75..57604117c5 100644 --- a/database/migrations/2020_04_22_055500_add_max_connections_column.php +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -8,10 +8,8 @@ class AddMaxConnectionsColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->integer('max_connections')->nullable()->default(0)->after('password'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropColumn('max_connections'); diff --git a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php index b0f859c9fc..af1b72e649 100644 --- a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php +++ b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php @@ -9,10 +9,8 @@ class AddBackupLimitToServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // Same as in the backups migration, we need to handle that plugin messing with the data structure @@ -35,10 +33,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('backup_limit'); diff --git a/database/migrations/2020_05_20_234655_add_mounts_table.php b/database/migrations/2020_05_20_234655_add_mounts_table.php index 09846a0a5d..db3b409d8c 100644 --- a/database/migrations/2020_05_20_234655_add_mounts_table.php +++ b/database/migrations/2020_05_20_234655_add_mounts_table.php @@ -8,10 +8,8 @@ class AddMountsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mounts', function (Blueprint $table) { $table->increments('id')->unique(); @@ -41,10 +39,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_node'); Schema::dropIfExists('egg_mount'); diff --git a/database/migrations/2020_05_21_192756_add_mount_server_table.php b/database/migrations/2020_05_21_192756_add_mount_server_table.php index 682bd578d9..7d8e9438b2 100644 --- a/database/migrations/2020_05_21_192756_add_mount_server_table.php +++ b/database/migrations/2020_05_21_192756_add_mount_server_table.php @@ -8,10 +8,8 @@ class AddMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mount_server', function (Blueprint $table) { $table->integer('server_id'); @@ -23,10 +21,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_server'); } diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php index 9b0743af23..11a6f513c7 100644 --- a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -8,10 +8,8 @@ class CreateUserRecoveryTokensTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('recovery_tokens', function (Blueprint $table) { $table->id(); @@ -25,10 +23,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('recovery_tokens'); } diff --git a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php index 711495edfd..a93b48053d 100644 --- a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php +++ b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php @@ -8,10 +8,8 @@ class AddNotesColumnForAllocations extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->string('notes')->nullable()->after('server_id'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('notes'); diff --git a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php index 9e6faa42b5..4ac99feddd 100644 --- a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php +++ b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php @@ -8,10 +8,8 @@ class AddBackupStateColumnToBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_successful'); diff --git a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php index e8e9c38f37..5c4adca1a5 100644 --- a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php +++ b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php @@ -8,10 +8,8 @@ class UpdateBytesToUnsignedBigint extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedBigInteger('bytes')->default(0)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->integer('bytes')->default(0)->change(); diff --git a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php index 0de248bfd5..763b204571 100644 --- a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php +++ b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php @@ -9,10 +9,8 @@ class ModifyChecksumsColumnForBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('sha256_hash', 'checksum'); @@ -25,10 +23,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('checksum', 'sha256_hash'); diff --git a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php index 638435a81f..53cba54f50 100644 --- a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php +++ b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php @@ -8,10 +8,8 @@ class DropPacksFromServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); @@ -21,10 +19,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack_id')->after('egg_id')->nullable(); diff --git a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php index 9bcce8d4db..7c051db2cc 100644 --- a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php +++ b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php @@ -8,10 +8,8 @@ class DropPacksFromApiKeyPermissions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('r_packs'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('r_packs')->default(0); diff --git a/database/migrations/2020_09_13_110047_drop_packs_table.php b/database/migrations/2020_09_13_110047_drop_packs_table.php index 4f83c0f2e7..58194b8fa0 100644 --- a/database/migrations/2020_09_13_110047_drop_packs_table.php +++ b/database/migrations/2020_09_13_110047_drop_packs_table.php @@ -8,20 +8,16 @@ class DropPacksTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('packs'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('packs', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php index 7b90d41b9b..274f9fd979 100644 --- a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php +++ b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php @@ -8,20 +8,16 @@ class DropDaemonKeyTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('daemon_keys'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php index 7420989a71..6a277e4495 100644 --- a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php +++ b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php @@ -8,10 +8,8 @@ class ChangeUniqueDatabaseNameToAccountForServer extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'database']); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'server_id', 'database']); diff --git a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php index 69593e656e..d0b3118e5a 100644 --- a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php +++ b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php @@ -9,10 +9,8 @@ class RemoveNullableFromScheduleNameField extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::update("UPDATE schedules SET name = 'Schedule' WHERE name IS NULL OR name = ''"); @@ -23,10 +21,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('name')->nullable()->change(); diff --git a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php index 1a001ae988..e134fe9a61 100644 --- a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php +++ b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php @@ -8,10 +8,8 @@ class AddFeaturesColumnToEggs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('features')->after('description')->nullable(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('features'); diff --git a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php index 776d3c6ba2..c7d2cff7f4 100644 --- a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php +++ b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php @@ -9,19 +9,22 @@ class SupportMultipleDockerImagesAndUpdates extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('docker_images')->after('docker_image')->nullable(); $table->text('update_url')->after('docker_images')->nullable(); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_images` = JSON_ARRAY(docker_image)'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_ARRAY(docker_image)')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('jsonb_build_array(docker_image)')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_image'); @@ -30,18 +33,22 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('docker_image')->after('docker_images'); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_image` = JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]"))'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + DB::table('eggs')->update(['docker_images' => DB::raw('docker_images->>0')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_images'); diff --git a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php index 0a28852844..4c28b6c2b1 100644 --- a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php +++ b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php @@ -8,10 +8,8 @@ class MakeSuccessfulNullableInServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->default(0)->change(); diff --git a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php index 1162d8a4ff..bc5d3356d9 100644 --- a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php +++ b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php @@ -9,27 +9,21 @@ class AddArchivedFieldToServerTransfersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('archived')->default(0)->after('new_additional_allocations'); }); // Update archived to all be true on existing transfers. - Schema::table('server_transfers', function (Blueprint $table) { - DB::statement('UPDATE `server_transfers` SET `archived` = 1 WHERE `successful` = 1'); - }); + DB::table('server_transfers')->where('successful', true)->update(['archived' => 1]); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->dropColumn('archived'); diff --git a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php index 2e1c50556b..771e06ab11 100644 --- a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php +++ b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php @@ -8,10 +8,8 @@ class AddUploadIdColumnToBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->text('upload_id')->nullable()->after('uuid'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('upload_id'); diff --git a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php index 8d617fc199..4a956625e3 100644 --- a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php +++ b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php @@ -8,10 +8,8 @@ class AddFileDenylistToEggConfigs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('file_denylist')->after('docker_images'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_01_13_013420_add_cron_month.php b/database/migrations/2021_01_13_013420_add_cron_month.php index 85e534248b..da0bf88418 100644 --- a/database/migrations/2021_01_13_013420_add_cron_month.php +++ b/database/migrations/2021_01_13_013420_add_cron_month.php @@ -8,10 +8,8 @@ class AddCronMonth extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('cron_month')->after('cron_day_of_week'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('cron_month'); diff --git a/database/migrations/2021_01_17_102401_create_audit_logs_table.php b/database/migrations/2021_01_17_102401_create_audit_logs_table.php index f67e7d647c..d0fddf801e 100644 --- a/database/migrations/2021_01_17_102401_create_audit_logs_table.php +++ b/database/migrations/2021_01_17_102401_create_audit_logs_table.php @@ -8,10 +8,8 @@ class CreateAuditLogsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('audit_logs', function (Blueprint $table) { $table->id(); @@ -32,10 +30,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('audit_logs'); } diff --git a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php index 12e6abb958..f8c9dec2ca 100644 --- a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php +++ b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php @@ -9,20 +9,16 @@ class AddGenericServerStatusColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('status')->nullable()->after('description'); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `status` = \'suspended\' WHERE `suspended` = 1'); - DB::update('UPDATE servers SET `status` = \'installing\' WHERE `installed` = 0'); - DB::update('UPDATE servers SET `status` = \'install_failed\' WHERE `installed` = 2'); - }); + DB::table('servers')->where('suspended', 1)->update(['status' => 'suspended']); + DB::table('servers')->where('installed', 1)->update(['status' => 'installing']); + DB::table('servers')->where('installed', 1)->update(['status' => 'install_failed']); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); @@ -32,21 +28,17 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedTinyInteger('suspended')->default(0); $table->unsignedTinyInteger('installed')->default(0); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `suspended` = 1 WHERE `status` = \'suspended\''); - DB::update('UPDATE servers SET `installed` = 1 WHERE `status` IS NULL'); - DB::update('UPDATE servers SET `installed` = 2 WHERE `status` = \'install_failed\''); - }); + DB::table('servers')->where('status', 'suspended')->update(['suspended' => 1]); + DB::table('servers')->whereNull('status')->update(['installed' => 1]); + DB::table('servers')->where('status', 'install_failed')->update(['installed' => 2]); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('status'); diff --git a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php index af49611353..7c2c5fa069 100644 --- a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php +++ b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php @@ -8,10 +8,8 @@ class UpdateFileDenylistToJson extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php index 888125468f..1fd63559c1 100644 --- a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php +++ b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php @@ -8,10 +8,8 @@ class AddIndexForServerAndAction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('audit_logs', function (Blueprint $table) { // Doing the index in this order lets me use the action alone without the server @@ -27,10 +25,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('audit_logs', function (Blueprint $table) { $table->dropIndex(['action', 'server_id']); diff --git a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php index 8eea84819e..b9196a0ae5 100644 --- a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php +++ b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php @@ -8,10 +8,8 @@ class MakeSftpPortUnsignedInt extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedSmallInteger('daemonSFTP')->default(2022)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->smallInteger('daemonSFTP')->default(2022)->change(); diff --git a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php index 57e1299523..61abdbd6c6 100644 --- a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php +++ b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php @@ -1,30 +1,22 @@ where('cron_month', '')->update(['cron_month' => '*']); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // No down function. } diff --git a/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php index 703f1524f9..0790454966 100644 --- a/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php +++ b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php @@ -8,10 +8,8 @@ class AddContinueOnFailureOptionToTasks extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->unsignedTinyInteger('continue_on_failure')->after('is_queued')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropColumn('continue_on_failure'); diff --git a/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php index 91bb43be91..a0143fdd6f 100644 --- a/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php +++ b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php @@ -8,10 +8,8 @@ class AddOnlyRunWhenServerOnlineOptionToSchedules extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->unsignedTinyInteger('only_when_online')->after('is_processing')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('only_when_online'); diff --git a/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php index bafa4dd76d..3296e3e8fb 100644 --- a/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php +++ b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php @@ -8,10 +8,8 @@ class AddSupportForLockingABackup extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedTinyInteger('is_locked')->after('is_successful')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_locked'); diff --git a/database/migrations/2021_07_12_013420_remove_userinteraction.php b/database/migrations/2021_07_12_013420_remove_userinteraction.php index 05321d4b33..33b4d5d3b4 100644 --- a/database/migrations/2021_07_12_013420_remove_userinteraction.php +++ b/database/migrations/2021_07_12_013420_remove_userinteraction.php @@ -7,22 +7,41 @@ class RemoveUserInteraction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Remove User Interaction from startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('config_startup::jsonb - \'userInteraction\''), + ]); + break; + } } - public function down() + /** + * Reverse the migrations. + */ + public function down(): void { // Add blank User Interaction array back to startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('jsonb_set(config_startup::jsonb, \'$.userInteraction\', jsonb_build_array())'), + ]); + break; + } } } diff --git a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php new file mode 100644 index 0000000000..a33bb4d310 --- /dev/null +++ b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->unsignedInteger('user_id'); + $table->string('name'); + $table->string('fingerprint'); + $table->text('public_key'); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_ssh_keys'); + } +} diff --git a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php index d47b0e5d2f..b9284eb0cc 100644 --- a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php +++ b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php @@ -9,10 +9,8 @@ class ChangeSuccessfulFieldToDefaultToFalseOnBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(false)->change(); @@ -26,10 +24,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true)->change(); diff --git a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php index fad8dc1936..5210f60d2b 100644 --- a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php +++ b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php @@ -9,10 +9,8 @@ class AddForeignKeysToMountNodeTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_node', function (Blueprint $table) { @@ -46,10 +44,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_node', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php index 9c5a403b20..f564ec491e 100644 --- a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php +++ b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_server', function (Blueprint $table) { @@ -45,10 +43,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_server', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php index 7bf99506b7..11285a5c54 100644 --- a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php +++ b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToEggMountTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('egg_mount', function (Blueprint $table) { @@ -45,10 +43,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_mount', function (Blueprint $table) { $table->dropForeign(['egg_id']); diff --git a/database/migrations/2022_01_25_030847_drop_google_analytics.php b/database/migrations/2022_01_25_030847_drop_google_analytics.php index 5daf0bc39e..0d65e5d4d9 100644 --- a/database/migrations/2022_01_25_030847_drop_google_analytics.php +++ b/database/migrations/2022_01_25_030847_drop_google_analytics.php @@ -7,25 +7,19 @@ class DropGoogleAnalytics extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::table('settings')->where('key', 'settings::app:analytics')->delete(); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { - DB::table('settings')->insert( - [ + DB::table('settings')->insert([ 'key' => 'settings::app:analytics', - ] - ); + ]); } } diff --git a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php new file mode 100644 index 0000000000..057ce9d743 --- /dev/null +++ b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php @@ -0,0 +1,38 @@ + value pairings to support naming the + * images provided. + */ + public function up(): void + { + DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { + $images = is_null($egg->docker_images) ? [] : json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR); + + $results = []; + foreach ($images as $key => $value) { + $results[is_int($key) ? $value : $key] = $value; + } + + DB::table('eggs')->where('id', $egg->id)->update(['docker_images' => $results]); + }); + } + + /** + * Reverse the migrations. This just keeps the values from the docker images array. + */ + public function down(): void + { + DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { + DB::table('eggs')->where('id', $egg->id)->update([ + 'docker_images' => array_values(json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR)), + ]); + }); + } +} diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php new file mode 100644 index 0000000000..4c4f05f1f1 --- /dev/null +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -0,0 +1,33 @@ +id(); + $table->uuid('batch')->nullable(); + $table->string('event')->index(); + $table->string('ip'); + $table->text('description')->nullable(); + $table->nullableNumericMorphs('actor'); + $table->json('properties'); + $table->timestamp('timestamp')->useCurrent()->onUpdate(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('activity_logs'); + } +} diff --git a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php new file mode 100644 index 0000000000..c9997ac419 --- /dev/null +++ b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php @@ -0,0 +1,28 @@ +id(); + $table->foreignId('activity_log_id')->references('id')->on('activity_logs')->cascadeOnDelete(); + $table->numericMorphs('subject'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('activity_log_subjects'); + } +} diff --git a/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php new file mode 100644 index 0000000000..f138786373 --- /dev/null +++ b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php @@ -0,0 +1,27 @@ +unsignedInteger('api_key_id')->nullable()->after('actor_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('activity_logs', function (Blueprint $table) { + $table->dropColumn('api_key_id'); + }); + } +}; diff --git a/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php new file mode 100644 index 0000000000..c5b30a49ae --- /dev/null +++ b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php @@ -0,0 +1,28 @@ +boolean('force_outgoing_ip')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eggs', function (Blueprint $table) { + $table->dropColumn('force_outgoing_ip'); + }); + } +} diff --git a/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php new file mode 100644 index 0000000000..541117a3aa --- /dev/null +++ b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php @@ -0,0 +1,28 @@ +timestamp('installed_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('installed_at'); + }); + } +} diff --git a/database/migrations/2022_12_12_213937_update_mail_settings_to_new_format.php b/database/migrations/2022_12_12_213937_update_mail_settings_to_new_format.php new file mode 100644 index 0000000000..97b651033b --- /dev/null +++ b/database/migrations/2022_12_12_213937_update_mail_settings_to_new_format.php @@ -0,0 +1,77 @@ +keys, 0); + $oldSettings = $settings->filter(fn (Setting $setting) => in_array($setting->key, $oldKeys)); + + // Gets the second column in our key table and gets all matching settings. + $newKeys = array_column($this->keys, 1); + $newSettings = $settings->filter(fn (Setting $setting) => in_array($setting->key, $newKeys)); + + // Map all the old settings to their new key. + $oldSettings->map(function (Setting $setting) use ($oldKeys) { + $row = array_search($setting->key, $oldKeys, true); + $setting->key = $this->keys[$row][1]; + + return $setting; + // Check if any settings with the new key already exist. + })->filter(function (Setting $setting) use ($newSettings) { + if ($newSettings->contains('key', $setting->key)) { + return false; + } + + return true; + // Update the settings to use their new keys if they don't already exist. + })->each(fn (Setting $setting) => $setting->save()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::transaction(function () { + $settings = Setting::all(); + + // Gets the second column in our key table and gets all matching settings. + $newKeys = array_column($this->keys, 0); + $newSettings = $settings->filter(fn (Setting $setting) => in_array($setting->key, $newKeys)); + + // Delete all settings that already have the new key. + $newSettings->each(fn (Setting $setting) => $setting->delete()); + + // Gets the first column in our key table and gets all matching settings. + $oldKeys = array_column($this->keys, 1); + $oldSettings = $settings->filter(fn (Setting $setting) => in_array($setting->key, $oldKeys)); + + // Map all the old settings to their new key. + $oldSettings->map(function (Setting $setting) use ($oldKeys) { + $row = array_search($setting->key, $oldKeys, true); + $setting->key = $this->keys[$row][0]; + + return $setting; + // Update the settings to use their new keys if they don't already exist. + })->each(fn (Setting $setting) => $setting->save()); + }); + } +}; diff --git a/database/migrations/2023_01_24_210051_add_uuid_column_to_failed_jobs_table.php b/database/migrations/2023_01_24_210051_add_uuid_column_to_failed_jobs_table.php new file mode 100644 index 0000000000..e3482e2789 --- /dev/null +++ b/database/migrations/2023_01_24_210051_add_uuid_column_to_failed_jobs_table.php @@ -0,0 +1,34 @@ +string('uuid')->after('id')->nullable()->unique(); + }); + + DB::table('failed_jobs')->whereNull('uuid')->cursor()->each(function ($job) { + DB::table('failed_jobs') + ->where('id', $job->id) + ->update(['uuid' => (string) Illuminate\Support\Str::uuid()]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('failed_jobs', function (Blueprint $table) { + $table->dropColumn('uuid'); + }); + } +}; diff --git a/database/migrations/2023_02_23_191004_add_expires_at_column_to_api_keys_table.php b/database/migrations/2023_02_23_191004_add_expires_at_column_to_api_keys_table.php new file mode 100644 index 0000000000..49e19bfedf --- /dev/null +++ b/database/migrations/2023_02_23_191004_add_expires_at_column_to_api_keys_table.php @@ -0,0 +1,27 @@ +timestamp('expires_at')->nullable()->after('last_used_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn('expires_at'); + }); + } +}; diff --git a/database/migrations/2024_07_13_091852_clear_unused_allocation_notes.php b/database/migrations/2024_07_13_091852_clear_unused_allocation_notes.php new file mode 100644 index 0000000000..4e29fe37e4 --- /dev/null +++ b/database/migrations/2024_07_13_091852_clear_unused_allocation_notes.php @@ -0,0 +1,26 @@ +whereNull('server_id') + ->update(['notes' => null]); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Reverse not needed + } +}; diff --git a/database/schema/mysql-schema.sql b/database/schema/mysql-schema.sql new file mode 100644 index 0000000000..bb03afc28b --- /dev/null +++ b/database/schema/mysql-schema.sql @@ -0,0 +1,878 @@ +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +DROP TABLE IF EXISTS `activity_log_subjects`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `activity_log_subjects` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `activity_log_id` bigint(20) unsigned NOT NULL, + `subject_type` varchar(191) NOT NULL, + `subject_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `activity_log_subjects_activity_log_id_foreign` (`activity_log_id`), + KEY `activity_log_subjects_subject_type_subject_id_index` (`subject_type`,`subject_id`), + CONSTRAINT `activity_log_subjects_activity_log_id_foreign` FOREIGN KEY (`activity_log_id`) REFERENCES `activity_logs` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `activity_logs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `activity_logs` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `batch` char(36) DEFAULT NULL, + `event` varchar(191) NOT NULL, + `ip` varchar(191) NOT NULL, + `description` text DEFAULT NULL, + `actor_type` varchar(191) DEFAULT NULL, + `actor_id` bigint(20) unsigned DEFAULT NULL, + `api_key_id` int(10) unsigned DEFAULT NULL, + `properties` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`properties`)), + `timestamp` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `activity_logs_actor_type_actor_id_index` (`actor_type`,`actor_id`), + KEY `activity_logs_event_index` (`event`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `allocations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `allocations` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `node_id` int(10) unsigned NOT NULL, + `ip` varchar(191) NOT NULL, + `ip_alias` text DEFAULT NULL, + `port` mediumint(8) unsigned NOT NULL, + `server_id` int(10) unsigned DEFAULT NULL, + `notes` varchar(191) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `allocations_node_id_ip_port_unique` (`node_id`,`ip`,`port`), + KEY `allocations_server_id_foreign` (`server_id`), + CONSTRAINT `allocations_node_id_foreign` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`) ON DELETE CASCADE, + CONSTRAINT `allocations_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `api_keys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `api_keys` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `key_type` tinyint(3) unsigned NOT NULL DEFAULT 0, + `identifier` char(16) DEFAULT NULL, + `token` text NOT NULL, + `allowed_ips` text DEFAULT NULL, + `memo` text DEFAULT NULL, + `last_used_at` timestamp NULL DEFAULT NULL, + `expires_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `r_servers` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_nodes` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_allocations` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_users` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_locations` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_nests` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_eggs` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_database_hosts` tinyint(3) unsigned NOT NULL DEFAULT 0, + `r_server_databases` tinyint(3) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `api_keys_identifier_unique` (`identifier`), + KEY `api_keys_user_id_foreign` (`user_id`), + CONSTRAINT `api_keys_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `api_logs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `api_logs` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `authorized` tinyint(1) NOT NULL, + `error` text DEFAULT NULL, + `key` char(16) DEFAULT NULL, + `method` char(6) NOT NULL, + `route` text NOT NULL, + `content` text DEFAULT NULL, + `user_agent` text NOT NULL, + `request_ip` varchar(45) NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `audit_logs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `audit_logs` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` char(36) NOT NULL, + `is_system` tinyint(1) NOT NULL DEFAULT 0, + `user_id` int(10) unsigned DEFAULT NULL, + `server_id` int(10) unsigned DEFAULT NULL, + `action` varchar(191) NOT NULL, + `subaction` varchar(191) DEFAULT NULL, + `device` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`device`)), + `metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`metadata`)), + `created_at` timestamp NOT NULL, + PRIMARY KEY (`id`), + KEY `audit_logs_user_id_foreign` (`user_id`), + KEY `audit_logs_server_id_foreign` (`server_id`), + KEY `audit_logs_action_server_id_index` (`action`,`server_id`), + CONSTRAINT `audit_logs_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE, + CONSTRAINT `audit_logs_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `backups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `backups` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `uuid` char(36) NOT NULL, + `upload_id` text DEFAULT NULL, + `is_successful` tinyint(1) NOT NULL DEFAULT 0, + `is_locked` tinyint(3) unsigned NOT NULL DEFAULT 0, + `name` varchar(191) NOT NULL, + `ignored_files` text NOT NULL, + `disk` varchar(191) NOT NULL, + `checksum` varchar(191) DEFAULT NULL, + `bytes` bigint(20) unsigned NOT NULL DEFAULT 0, + `completed_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `backups_uuid_unique` (`uuid`), + KEY `backups_server_id_foreign` (`server_id`), + CONSTRAINT `backups_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `database_hosts`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `database_hosts` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) NOT NULL, + `host` varchar(191) NOT NULL, + `port` int(10) unsigned NOT NULL, + `username` varchar(191) NOT NULL, + `password` text NOT NULL, + `max_databases` int(10) unsigned DEFAULT NULL, + `node_id` int(10) unsigned DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `database_hosts_node_id_foreign` (`node_id`), + CONSTRAINT `database_hosts_node_id_foreign` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `databases`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `databases` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `database_host_id` int(10) unsigned NOT NULL, + `database` varchar(191) NOT NULL, + `username` varchar(191) NOT NULL, + `remote` varchar(191) NOT NULL DEFAULT '%', + `password` text NOT NULL, + `max_connections` int(11) DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `databases_database_host_id_username_unique` (`database_host_id`,`username`), + UNIQUE KEY `databases_database_host_id_server_id_database_unique` (`database_host_id`,`server_id`,`database`), + KEY `databases_server_id_foreign` (`server_id`), + CONSTRAINT `databases_database_host_id_foreign` FOREIGN KEY (`database_host_id`) REFERENCES `database_hosts` (`id`), + CONSTRAINT `databases_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `egg_mount`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `egg_mount` ( + `egg_id` int(10) unsigned NOT NULL, + `mount_id` int(10) unsigned NOT NULL, + UNIQUE KEY `egg_mount_egg_id_mount_id_unique` (`egg_id`,`mount_id`), + KEY `egg_mount_mount_id_foreign` (`mount_id`), + CONSTRAINT `egg_mount_egg_id_foreign` FOREIGN KEY (`egg_id`) REFERENCES `eggs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `egg_mount_mount_id_foreign` FOREIGN KEY (`mount_id`) REFERENCES `mounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `egg_variables`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `egg_variables` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `egg_id` int(10) unsigned NOT NULL, + `name` varchar(191) NOT NULL, + `description` text NOT NULL, + `env_variable` varchar(191) NOT NULL, + `default_value` text NOT NULL, + `user_viewable` tinyint(3) unsigned NOT NULL, + `user_editable` tinyint(3) unsigned NOT NULL, + `rules` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `service_variables_egg_id_foreign` (`egg_id`), + CONSTRAINT `service_variables_egg_id_foreign` FOREIGN KEY (`egg_id`) REFERENCES `eggs` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `eggs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `eggs` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` char(36) NOT NULL, + `nest_id` int(10) unsigned NOT NULL, + `author` varchar(191) NOT NULL, + `name` varchar(191) NOT NULL, + `description` text DEFAULT NULL, + `features` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`features`)), + `docker_images` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`docker_images`)), + `file_denylist` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`file_denylist`)), + `update_url` text DEFAULT NULL, + `config_files` text DEFAULT NULL, + `config_startup` text DEFAULT NULL, + `config_logs` text DEFAULT NULL, + `config_stop` varchar(191) DEFAULT NULL, + `config_from` int(10) unsigned DEFAULT NULL, + `startup` text DEFAULT NULL, + `script_container` varchar(191) NOT NULL DEFAULT 'alpine:3.4', + `copy_script_from` int(10) unsigned DEFAULT NULL, + `script_entry` varchar(191) NOT NULL DEFAULT 'ash', + `script_is_privileged` tinyint(1) NOT NULL DEFAULT 1, + `script_install` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `force_outgoing_ip` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `service_options_uuid_unique` (`uuid`), + KEY `service_options_nest_id_foreign` (`nest_id`), + KEY `eggs_config_from_foreign` (`config_from`), + KEY `eggs_copy_script_from_foreign` (`copy_script_from`), + CONSTRAINT `eggs_config_from_foreign` FOREIGN KEY (`config_from`) REFERENCES `eggs` (`id`) ON DELETE SET NULL, + CONSTRAINT `eggs_copy_script_from_foreign` FOREIGN KEY (`copy_script_from`) REFERENCES `eggs` (`id`) ON DELETE SET NULL, + CONSTRAINT `service_options_nest_id_foreign` FOREIGN KEY (`nest_id`) REFERENCES `nests` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `failed_jobs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `failed_jobs` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(191) DEFAULT NULL, + `connection` text NOT NULL, + `queue` text NOT NULL, + `payload` longtext NOT NULL, + `failed_at` timestamp NOT NULL, + `exception` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `jobs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `jobs` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `queue` varchar(191) NOT NULL, + `payload` longtext NOT NULL, + `attempts` tinyint(3) unsigned NOT NULL, + `reserved_at` int(10) unsigned DEFAULT NULL, + `available_at` int(10) unsigned NOT NULL, + `created_at` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `jobs_queue_reserved_at_index` (`queue`,`reserved_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `locations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `locations` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `short` varchar(191) NOT NULL, + `long` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `locations_short_unique` (`short`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `migrations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `migrations` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `migration` varchar(191) NOT NULL, + `batch` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `mount_node`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `mount_node` ( + `node_id` int(10) unsigned NOT NULL, + `mount_id` int(10) unsigned NOT NULL, + UNIQUE KEY `mount_node_node_id_mount_id_unique` (`node_id`,`mount_id`), + KEY `mount_node_mount_id_foreign` (`mount_id`), + CONSTRAINT `mount_node_mount_id_foreign` FOREIGN KEY (`mount_id`) REFERENCES `mounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `mount_node_node_id_foreign` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `mount_server`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `mount_server` ( + `server_id` int(10) unsigned NOT NULL, + `mount_id` int(10) unsigned NOT NULL, + UNIQUE KEY `mount_server_server_id_mount_id_unique` (`server_id`,`mount_id`), + KEY `mount_server_mount_id_foreign` (`mount_id`), + CONSTRAINT `mount_server_mount_id_foreign` FOREIGN KEY (`mount_id`) REFERENCES `mounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `mount_server_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `mounts`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `mounts` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` char(36) NOT NULL, + `name` varchar(191) NOT NULL, + `description` text DEFAULT NULL, + `source` varchar(191) NOT NULL, + `target` varchar(191) NOT NULL, + `read_only` tinyint(3) unsigned NOT NULL, + `user_mountable` tinyint(3) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `mounts_id_unique` (`id`), + UNIQUE KEY `mounts_uuid_unique` (`uuid`), + UNIQUE KEY `mounts_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `nests`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `nests` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` char(36) NOT NULL, + `author` char(191) NOT NULL, + `name` varchar(191) NOT NULL, + `description` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `services_uuid_unique` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `nodes`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `nodes` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` char(36) NOT NULL, + `public` smallint(5) unsigned NOT NULL, + `name` varchar(191) NOT NULL, + `description` text DEFAULT NULL, + `location_id` int(10) unsigned NOT NULL, + `fqdn` varchar(191) NOT NULL, + `scheme` varchar(191) NOT NULL DEFAULT 'https', + `behind_proxy` tinyint(1) NOT NULL DEFAULT 0, + `maintenance_mode` tinyint(1) NOT NULL DEFAULT 0, + `memory` int(10) unsigned NOT NULL, + `memory_overallocate` int(11) NOT NULL DEFAULT 0, + `disk` int(10) unsigned NOT NULL, + `disk_overallocate` int(11) NOT NULL DEFAULT 0, + `upload_size` int(10) unsigned NOT NULL DEFAULT 100, + `daemon_token_id` char(16) NOT NULL, + `daemon_token` text NOT NULL, + `daemonListen` smallint(5) unsigned NOT NULL DEFAULT 8080, + `daemonSFTP` smallint(5) unsigned NOT NULL DEFAULT 2022, + `daemonBase` varchar(191) NOT NULL DEFAULT '/home/daemon-files', + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `nodes_uuid_unique` (`uuid`), + UNIQUE KEY `nodes_daemon_token_id_unique` (`daemon_token_id`), + KEY `nodes_location_id_foreign` (`location_id`), + CONSTRAINT `nodes_location_id_foreign` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `notifications`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `notifications` ( + `id` varchar(191) NOT NULL, + `type` varchar(191) NOT NULL, + `notifiable_type` varchar(191) NOT NULL, + `notifiable_id` bigint(20) unsigned NOT NULL, + `data` text NOT NULL, + `read_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `notifications_notifiable_type_notifiable_id_index` (`notifiable_type`,`notifiable_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `password_resets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `password_resets` ( + `email` varchar(191) NOT NULL, + `token` varchar(191) NOT NULL, + `created_at` timestamp NOT NULL, + KEY `password_resets_email_index` (`email`), + KEY `password_resets_token_index` (`token`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `recovery_tokens`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `recovery_tokens` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `token` varchar(191) NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `recovery_tokens_user_id_foreign` (`user_id`), + CONSTRAINT `recovery_tokens_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `schedules`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `schedules` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `name` varchar(191) NOT NULL, + `cron_day_of_week` varchar(191) NOT NULL, + `cron_month` varchar(191) NOT NULL, + `cron_day_of_month` varchar(191) NOT NULL, + `cron_hour` varchar(191) NOT NULL, + `cron_minute` varchar(191) NOT NULL, + `is_active` tinyint(1) NOT NULL, + `is_processing` tinyint(1) NOT NULL, + `only_when_online` tinyint(3) unsigned NOT NULL DEFAULT 0, + `last_run_at` timestamp NULL DEFAULT NULL, + `next_run_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `schedules_server_id_foreign` (`server_id`), + CONSTRAINT `schedules_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `server_transfers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `server_transfers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `successful` tinyint(1) DEFAULT NULL, + `old_node` int(10) unsigned NOT NULL, + `new_node` int(10) unsigned NOT NULL, + `old_allocation` int(10) unsigned NOT NULL, + `new_allocation` int(10) unsigned NOT NULL, + `old_additional_allocations` longtext DEFAULT NULL COMMENT '(DC2Type:json)', + `new_additional_allocations` longtext DEFAULT NULL COMMENT '(DC2Type:json)', + `archived` tinyint(1) NOT NULL DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `server_transfers_server_id_foreign` (`server_id`), + CONSTRAINT `server_transfers_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `server_variables`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `server_variables` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned DEFAULT NULL, + `variable_id` int(10) unsigned NOT NULL, + `variable_value` text NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `server_variables_server_id_foreign` (`server_id`), + KEY `server_variables_variable_id_foreign` (`variable_id`), + CONSTRAINT `server_variables_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE, + CONSTRAINT `server_variables_variable_id_foreign` FOREIGN KEY (`variable_id`) REFERENCES `egg_variables` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `servers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `servers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `external_id` varchar(191) DEFAULT NULL, + `uuid` char(36) NOT NULL, + `uuidShort` char(8) NOT NULL, + `node_id` int(10) unsigned NOT NULL, + `name` varchar(191) NOT NULL, + `description` text NOT NULL, + `status` varchar(191) DEFAULT NULL, + `skip_scripts` tinyint(1) NOT NULL DEFAULT 0, + `owner_id` int(10) unsigned NOT NULL, + `memory` int(10) unsigned NOT NULL, + `swap` int(11) NOT NULL, + `disk` int(10) unsigned NOT NULL, + `io` int(10) unsigned NOT NULL, + `cpu` int(10) unsigned NOT NULL, + `threads` varchar(191) DEFAULT NULL, + `oom_disabled` tinyint(3) unsigned NOT NULL DEFAULT 0, + `allocation_id` int(10) unsigned NOT NULL, + `nest_id` int(10) unsigned NOT NULL, + `egg_id` int(10) unsigned NOT NULL, + `startup` text NOT NULL, + `image` varchar(191) NOT NULL, + `allocation_limit` int(10) unsigned DEFAULT NULL, + `database_limit` int(10) unsigned DEFAULT 0, + `backup_limit` int(10) unsigned NOT NULL DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `installed_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `servers_uuid_unique` (`uuid`), + UNIQUE KEY `servers_uuidshort_unique` (`uuidShort`), + UNIQUE KEY `servers_allocation_id_unique` (`allocation_id`), + UNIQUE KEY `servers_external_id_unique` (`external_id`), + KEY `servers_node_id_foreign` (`node_id`), + KEY `servers_owner_id_foreign` (`owner_id`), + KEY `servers_nest_id_foreign` (`nest_id`), + KEY `servers_egg_id_foreign` (`egg_id`), + CONSTRAINT `servers_allocation_id_foreign` FOREIGN KEY (`allocation_id`) REFERENCES `allocations` (`id`), + CONSTRAINT `servers_egg_id_foreign` FOREIGN KEY (`egg_id`) REFERENCES `eggs` (`id`), + CONSTRAINT `servers_nest_id_foreign` FOREIGN KEY (`nest_id`) REFERENCES `nests` (`id`), + CONSTRAINT `servers_node_id_foreign` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`), + CONSTRAINT `servers_owner_id_foreign` FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `sessions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `sessions` ( + `id` varchar(191) NOT NULL, + `user_id` int(11) DEFAULT NULL, + `ip_address` varchar(45) DEFAULT NULL, + `user_agent` text DEFAULT NULL, + `payload` text NOT NULL, + `last_activity` int(11) NOT NULL, + UNIQUE KEY `sessions_id_unique` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `settings` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(191) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `settings_key_unique` (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `subusers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `subusers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `server_id` int(10) unsigned NOT NULL, + `permissions` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`permissions`)), + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `subusers_user_id_foreign` (`user_id`), + KEY `subusers_server_id_foreign` (`server_id`), + CONSTRAINT `subusers_server_id_foreign` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE CASCADE, + CONSTRAINT `subusers_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tasks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tasks` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `schedule_id` int(10) unsigned NOT NULL, + `sequence_id` int(10) unsigned NOT NULL, + `action` varchar(191) NOT NULL, + `payload` text NOT NULL, + `time_offset` int(10) unsigned NOT NULL, + `is_queued` tinyint(1) NOT NULL, + `continue_on_failure` tinyint(3) unsigned NOT NULL DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `tasks_schedule_id_sequence_id_index` (`schedule_id`,`sequence_id`), + CONSTRAINT `tasks_schedule_id_foreign` FOREIGN KEY (`schedule_id`) REFERENCES `schedules` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tasks_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tasks_log` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `task_id` int(10) unsigned NOT NULL, + `run_time` timestamp NOT NULL, + `run_status` int(10) unsigned NOT NULL, + `response` text NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `user_ssh_keys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user_ssh_keys` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `name` varchar(191) NOT NULL, + `fingerprint` varchar(191) NOT NULL, + `public_key` text NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_ssh_keys_user_id_foreign` (`user_id`), + CONSTRAINT `user_ssh_keys_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `external_id` varchar(191) DEFAULT NULL, + `uuid` char(36) NOT NULL, + `username` varchar(191) NOT NULL, + `email` varchar(191) NOT NULL, + `name_first` varchar(191) DEFAULT NULL, + `name_last` varchar(191) DEFAULT NULL, + `password` text NOT NULL, + `remember_token` varchar(191) DEFAULT NULL, + `language` char(5) NOT NULL DEFAULT 'en', + `root_admin` tinyint(3) unsigned NOT NULL DEFAULT 0, + `use_totp` tinyint(3) unsigned NOT NULL, + `totp_secret` text DEFAULT NULL, + `totp_authenticated_at` timestamp NULL DEFAULT NULL, + `gravatar` tinyint(1) NOT NULL DEFAULT 1, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `users_uuid_unique` (`uuid`), + UNIQUE KEY `users_email_unique` (`email`), + UNIQUE KEY `users_username_unique` (`username`), + KEY `users_external_id_index` (`external_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +INSERT INTO `migrations` VALUES (1,'2016_01_23_195641_add_allocations_table',1); +INSERT INTO `migrations` VALUES (2,'2016_01_23_195851_add_api_keys',1); +INSERT INTO `migrations` VALUES (3,'2016_01_23_200044_add_api_permissions',1); +INSERT INTO `migrations` VALUES (4,'2016_01_23_200159_add_downloads',1); +INSERT INTO `migrations` VALUES (5,'2016_01_23_200421_create_failed_jobs_table',1); +INSERT INTO `migrations` VALUES (6,'2016_01_23_200440_create_jobs_table',1); +INSERT INTO `migrations` VALUES (7,'2016_01_23_200528_add_locations',1); +INSERT INTO `migrations` VALUES (8,'2016_01_23_200648_add_nodes',1); +INSERT INTO `migrations` VALUES (9,'2016_01_23_201433_add_password_resets',1); +INSERT INTO `migrations` VALUES (10,'2016_01_23_201531_add_permissions',1); +INSERT INTO `migrations` VALUES (11,'2016_01_23_201649_add_server_variables',1); +INSERT INTO `migrations` VALUES (12,'2016_01_23_201748_add_servers',1); +INSERT INTO `migrations` VALUES (13,'2016_01_23_202544_add_service_options',1); +INSERT INTO `migrations` VALUES (14,'2016_01_23_202731_add_service_varibles',1); +INSERT INTO `migrations` VALUES (15,'2016_01_23_202943_add_services',1); +INSERT INTO `migrations` VALUES (16,'2016_01_23_203119_create_settings_table',1); +INSERT INTO `migrations` VALUES (17,'2016_01_23_203150_add_subusers',1); +INSERT INTO `migrations` VALUES (18,'2016_01_23_203159_add_users',1); +INSERT INTO `migrations` VALUES (19,'2016_01_23_203947_create_sessions_table',1); +INSERT INTO `migrations` VALUES (20,'2016_01_25_234418_rename_permissions_column',1); +INSERT INTO `migrations` VALUES (21,'2016_02_07_172148_add_databases_tables',1); +INSERT INTO `migrations` VALUES (22,'2016_02_07_181319_add_database_servers_table',1); +INSERT INTO `migrations` VALUES (23,'2016_02_13_154306_add_service_option_default_startup',1); +INSERT INTO `migrations` VALUES (24,'2016_02_20_155318_add_unique_service_field',1); +INSERT INTO `migrations` VALUES (25,'2016_02_27_163411_add_tasks_table',1); +INSERT INTO `migrations` VALUES (26,'2016_02_27_163447_add_tasks_log_table',1); +INSERT INTO `migrations` VALUES (27,'2016_03_18_155649_add_nullable_field_lastrun',1); +INSERT INTO `migrations` VALUES (28,'2016_08_30_212718_add_ip_alias',1); +INSERT INTO `migrations` VALUES (29,'2016_08_30_213301_modify_ip_storage_method',1); +INSERT INTO `migrations` VALUES (30,'2016_09_01_193520_add_suspension_for_servers',1); +INSERT INTO `migrations` VALUES (31,'2016_09_01_211924_remove_active_column',1); +INSERT INTO `migrations` VALUES (32,'2016_09_02_190647_add_sftp_password_storage',1); +INSERT INTO `migrations` VALUES (33,'2016_09_04_171338_update_jobs_tables',1); +INSERT INTO `migrations` VALUES (34,'2016_09_04_172028_update_failed_jobs_table',1); +INSERT INTO `migrations` VALUES (35,'2016_09_04_182835_create_notifications_table',1); +INSERT INTO `migrations` VALUES (36,'2016_09_07_163017_add_unique_identifier',1); +INSERT INTO `migrations` VALUES (37,'2016_09_14_145945_allow_longer_regex_field',1); +INSERT INTO `migrations` VALUES (38,'2016_09_17_194246_add_docker_image_column',1); +INSERT INTO `migrations` VALUES (39,'2016_09_21_165554_update_servers_column_name',1); +INSERT INTO `migrations` VALUES (40,'2016_09_29_213518_rename_double_insurgency',1); +INSERT INTO `migrations` VALUES (41,'2016_10_07_152117_build_api_log_table',1); +INSERT INTO `migrations` VALUES (42,'2016_10_14_164802_update_api_keys',1); +INSERT INTO `migrations` VALUES (43,'2016_10_23_181719_update_misnamed_bungee',1); +INSERT INTO `migrations` VALUES (44,'2016_10_23_193810_add_foreign_keys_servers',1); +INSERT INTO `migrations` VALUES (45,'2016_10_23_201624_add_foreign_allocations',1); +INSERT INTO `migrations` VALUES (46,'2016_10_23_202222_add_foreign_api_keys',1); +INSERT INTO `migrations` VALUES (47,'2016_10_23_202703_add_foreign_api_permissions',1); +INSERT INTO `migrations` VALUES (48,'2016_10_23_202953_add_foreign_database_servers',1); +INSERT INTO `migrations` VALUES (49,'2016_10_23_203105_add_foreign_databases',1); +INSERT INTO `migrations` VALUES (50,'2016_10_23_203335_add_foreign_nodes',1); +INSERT INTO `migrations` VALUES (51,'2016_10_23_203522_add_foreign_permissions',1); +INSERT INTO `migrations` VALUES (52,'2016_10_23_203857_add_foreign_server_variables',1); +INSERT INTO `migrations` VALUES (53,'2016_10_23_204157_add_foreign_service_options',1); +INSERT INTO `migrations` VALUES (54,'2016_10_23_204321_add_foreign_service_variables',1); +INSERT INTO `migrations` VALUES (55,'2016_10_23_204454_add_foreign_subusers',1); +INSERT INTO `migrations` VALUES (56,'2016_10_23_204610_add_foreign_tasks',1); +INSERT INTO `migrations` VALUES (57,'2016_11_04_000949_add_ark_service_option_fixed',1); +INSERT INTO `migrations` VALUES (58,'2016_11_11_220649_add_pack_support',1); +INSERT INTO `migrations` VALUES (59,'2016_11_11_231731_set_service_name_unique',1); +INSERT INTO `migrations` VALUES (60,'2016_11_27_142519_add_pack_column',1); +INSERT INTO `migrations` VALUES (61,'2016_12_01_173018_add_configurable_upload_limit',1); +INSERT INTO `migrations` VALUES (62,'2016_12_02_185206_correct_service_variables',1); +INSERT INTO `migrations` VALUES (63,'2017_01_03_150436_fix_misnamed_option_tag',1); +INSERT INTO `migrations` VALUES (64,'2017_01_07_154228_create_node_configuration_tokens_table',1); +INSERT INTO `migrations` VALUES (65,'2017_01_12_135449_add_more_user_data',1); +INSERT INTO `migrations` VALUES (66,'2017_02_02_175548_UpdateColumnNames',1); +INSERT INTO `migrations` VALUES (67,'2017_02_03_140948_UpdateNodesTable',1); +INSERT INTO `migrations` VALUES (68,'2017_02_03_155554_RenameColumns',1); +INSERT INTO `migrations` VALUES (69,'2017_02_05_164123_AdjustColumnNames',1); +INSERT INTO `migrations` VALUES (70,'2017_02_05_164516_AdjustColumnNamesForServicePacks',1); +INSERT INTO `migrations` VALUES (71,'2017_02_09_174834_SetupPermissionsPivotTable',1); +INSERT INTO `migrations` VALUES (72,'2017_02_10_171858_UpdateAPIKeyColumnNames',1); +INSERT INTO `migrations` VALUES (73,'2017_03_03_224254_UpdateNodeConfigTokensColumns',1); +INSERT INTO `migrations` VALUES (74,'2017_03_05_212803_DeleteServiceExecutableOption',1); +INSERT INTO `migrations` VALUES (75,'2017_03_10_162934_AddNewServiceOptionsColumns',1); +INSERT INTO `migrations` VALUES (76,'2017_03_10_173607_MigrateToNewServiceSystem',1); +INSERT INTO `migrations` VALUES (77,'2017_03_11_215455_ChangeServiceVariablesValidationRules',1); +INSERT INTO `migrations` VALUES (78,'2017_03_12_150648_MoveFunctionsFromFileToDatabase',1); +INSERT INTO `migrations` VALUES (79,'2017_03_14_175631_RenameServicePacksToSingluarPacks',1); +INSERT INTO `migrations` VALUES (80,'2017_03_14_200326_AddLockedStatusToTable',1); +INSERT INTO `migrations` VALUES (81,'2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost',1); +INSERT INTO `migrations` VALUES (82,'2017_03_16_181515_CleanupDatabasesDatabase',1); +INSERT INTO `migrations` VALUES (83,'2017_03_18_204953_AddForeignKeyToPacks',1); +INSERT INTO `migrations` VALUES (84,'2017_03_31_221948_AddServerDescriptionColumn',1); +INSERT INTO `migrations` VALUES (85,'2017_04_02_163232_DropDeletedAtColumnFromServers',1); +INSERT INTO `migrations` VALUES (86,'2017_04_15_125021_UpgradeTaskSystem',1); +INSERT INTO `migrations` VALUES (87,'2017_04_20_171943_AddScriptsToServiceOptions',1); +INSERT INTO `migrations` VALUES (88,'2017_04_21_151432_AddServiceScriptTrackingToServers',1); +INSERT INTO `migrations` VALUES (89,'2017_04_27_145300_AddCopyScriptFromColumn',1); +INSERT INTO `migrations` VALUES (90,'2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy',1); +INSERT INTO `migrations` VALUES (91,'2017_05_01_141528_DeleteDownloadTable',1); +INSERT INTO `migrations` VALUES (92,'2017_05_01_141559_DeleteNodeConfigurationTable',1); +INSERT INTO `migrations` VALUES (93,'2017_06_10_152951_add_external_id_to_users',1); +INSERT INTO `migrations` VALUES (94,'2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete',1); +INSERT INTO `migrations` VALUES (95,'2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion',1); +INSERT INTO `migrations` VALUES (96,'2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete',1); +INSERT INTO `migrations` VALUES (97,'2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted',1); +INSERT INTO `migrations` VALUES (98,'2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted',1); +INSERT INTO `migrations` VALUES (99,'2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted',1); +INSERT INTO `migrations` VALUES (100,'2017_08_05_144104_AllowNegativeValuesForOverallocation',1); +INSERT INTO `migrations` VALUES (101,'2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields',1); +INSERT INTO `migrations` VALUES (102,'2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted',1); +INSERT INTO `migrations` VALUES (103,'2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted',1); +INSERT INTO `migrations` VALUES (104,'2017_09_10_225749_RenameTasksTableForStructureRefactor',1); +INSERT INTO `migrations` VALUES (105,'2017_09_10_225941_CreateSchedulesTable',1); +INSERT INTO `migrations` VALUES (106,'2017_09_10_230309_CreateNewTasksTableForSchedules',1); +INSERT INTO `migrations` VALUES (107,'2017_09_11_002938_TransferOldTasksToNewScheduler',1); +INSERT INTO `migrations` VALUES (108,'2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem',1); +INSERT INTO `migrations` VALUES (109,'2017_09_23_170933_CreateDaemonKeysTable',1); +INSERT INTO `migrations` VALUES (110,'2017_09_23_173628_RemoveDaemonSecretFromServersTable',1); +INSERT INTO `migrations` VALUES (111,'2017_09_23_185022_RemoveDaemonSecretFromSubusersTable',1); +INSERT INTO `migrations` VALUES (112,'2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier',1); +INSERT INTO `migrations` VALUES (113,'2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration',1); +INSERT INTO `migrations` VALUES (114,'2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted',1); +INSERT INTO `migrations` VALUES (115,'2017_10_06_214026_ServicesToNestsConversion',1); +INSERT INTO `migrations` VALUES (116,'2017_10_06_214053_ServiceOptionsToEggsConversion',1); +INSERT INTO `migrations` VALUES (117,'2017_10_06_215741_ServiceVariablesToEggVariablesConversion',1); +INSERT INTO `migrations` VALUES (118,'2017_10_24_222238_RemoveLegacySFTPInformation',1); +INSERT INTO `migrations` VALUES (119,'2017_11_11_161922_Add2FaLastAuthorizationTimeColumn',1); +INSERT INTO `migrations` VALUES (120,'2017_11_19_122708_MigratePubPrivFormatToSingleKey',1); +INSERT INTO `migrations` VALUES (121,'2017_12_04_184012_DropAllocationsWhenNodeIsDeleted',1); +INSERT INTO `migrations` VALUES (122,'2017_12_12_220426_MigrateSettingsTableToNewFormat',1); +INSERT INTO `migrations` VALUES (123,'2018_01_01_122821_AllowNegativeValuesForServerSwap',1); +INSERT INTO `migrations` VALUES (124,'2018_01_11_213943_AddApiKeyPermissionColumns',1); +INSERT INTO `migrations` VALUES (125,'2018_01_13_142012_SetupTableForKeyEncryption',1); +INSERT INTO `migrations` VALUES (126,'2018_01_13_145209_AddLastUsedAtColumn',1); +INSERT INTO `migrations` VALUES (127,'2018_02_04_145617_AllowTextInUserExternalId',1); +INSERT INTO `migrations` VALUES (128,'2018_02_10_151150_remove_unique_index_on_external_id_column',1); +INSERT INTO `migrations` VALUES (129,'2018_02_17_134254_ensure_unique_allocation_id_on_servers_table',1); +INSERT INTO `migrations` VALUES (130,'2018_02_24_112356_add_external_id_column_to_servers_table',1); +INSERT INTO `migrations` VALUES (131,'2018_02_25_160152_remove_default_null_value_on_table',1); +INSERT INTO `migrations` VALUES (132,'2018_02_25_160604_define_unique_index_on_users_external_id',1); +INSERT INTO `migrations` VALUES (133,'2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table',1); +INSERT INTO `migrations` VALUES (134,'2018_03_15_124536_add_description_to_nodes',1); +INSERT INTO `migrations` VALUES (135,'2018_05_04_123826_add_maintenance_to_nodes',1); +INSERT INTO `migrations` VALUES (136,'2018_09_03_143756_allow_egg_variables_to_have_longer_values',1); +INSERT INTO `migrations` VALUES (137,'2018_09_03_144005_allow_server_variables_to_have_longer_values',1); +INSERT INTO `migrations` VALUES (138,'2019_03_02_142328_set_allocation_limit_default_null',1); +INSERT INTO `migrations` VALUES (139,'2019_03_02_151321_fix_unique_index_to_account_for_host',1); +INSERT INTO `migrations` VALUES (140,'2020_03_22_163911_merge_permissions_table_into_subusers',1); +INSERT INTO `migrations` VALUES (141,'2020_03_22_164814_drop_permissions_table',1); +INSERT INTO `migrations` VALUES (142,'2020_04_03_203624_add_threads_column_to_servers_table',1); +INSERT INTO `migrations` VALUES (143,'2020_04_03_230614_create_backups_table',1); +INSERT INTO `migrations` VALUES (144,'2020_04_04_131016_add_table_server_transfers',1); +INSERT INTO `migrations` VALUES (145,'2020_04_10_141024_store_node_tokens_as_encrypted_value',1); +INSERT INTO `migrations` VALUES (146,'2020_04_17_203438_allow_nullable_descriptions',1); +INSERT INTO `migrations` VALUES (147,'2020_04_22_055500_add_max_connections_column',1); +INSERT INTO `migrations` VALUES (148,'2020_04_26_111208_add_backup_limit_to_servers',1); +INSERT INTO `migrations` VALUES (149,'2020_05_20_234655_add_mounts_table',1); +INSERT INTO `migrations` VALUES (150,'2020_05_21_192756_add_mount_server_table',1); +INSERT INTO `migrations` VALUES (151,'2020_07_02_213612_create_user_recovery_tokens_table',1); +INSERT INTO `migrations` VALUES (152,'2020_07_09_201845_add_notes_column_for_allocations',1); +INSERT INTO `migrations` VALUES (153,'2020_08_20_205533_add_backup_state_column_to_backups',1); +INSERT INTO `migrations` VALUES (154,'2020_08_22_132500_update_bytes_to_unsigned_bigint',1); +INSERT INTO `migrations` VALUES (155,'2020_08_23_175331_modify_checksums_column_for_backups',1); +INSERT INTO `migrations` VALUES (156,'2020_09_13_110007_drop_packs_from_servers',1); +INSERT INTO `migrations` VALUES (157,'2020_09_13_110021_drop_packs_from_api_key_permissions',1); +INSERT INTO `migrations` VALUES (158,'2020_09_13_110047_drop_packs_table',1); +INSERT INTO `migrations` VALUES (159,'2020_09_13_113503_drop_daemon_key_table',1); +INSERT INTO `migrations` VALUES (160,'2020_10_10_165437_change_unique_database_name_to_account_for_server',1); +INSERT INTO `migrations` VALUES (161,'2020_10_26_194904_remove_nullable_from_schedule_name_field',1); +INSERT INTO `migrations` VALUES (162,'2020_11_02_201014_add_features_column_to_eggs',1); +INSERT INTO `migrations` VALUES (163,'2020_12_12_102435_support_multiple_docker_images_and_updates',1); +INSERT INTO `migrations` VALUES (164,'2020_12_14_013707_make_successful_nullable_in_server_transfers',1); +INSERT INTO `migrations` VALUES (165,'2020_12_17_014330_add_archived_field_to_server_transfers_table',1); +INSERT INTO `migrations` VALUES (166,'2020_12_24_092449_make_allocation_fields_json',1); +INSERT INTO `migrations` VALUES (167,'2020_12_26_184914_add_upload_id_column_to_backups_table',1); +INSERT INTO `migrations` VALUES (168,'2021_01_10_153937_add_file_denylist_to_egg_configs',1); +INSERT INTO `migrations` VALUES (169,'2021_01_13_013420_add_cron_month',1); +INSERT INTO `migrations` VALUES (170,'2021_01_17_102401_create_audit_logs_table',1); +INSERT INTO `migrations` VALUES (171,'2021_01_17_152623_add_generic_server_status_column',1); +INSERT INTO `migrations` VALUES (172,'2021_01_26_210502_update_file_denylist_to_json',1); +INSERT INTO `migrations` VALUES (173,'2021_02_23_205021_add_index_for_server_and_action',1); +INSERT INTO `migrations` VALUES (174,'2021_02_23_212657_make_sftp_port_unsigned_int',1); +INSERT INTO `migrations` VALUES (175,'2021_03_21_104718_force_cron_month_field_to_have_value_if_missing',1); +INSERT INTO `migrations` VALUES (176,'2021_05_01_092457_add_continue_on_failure_option_to_tasks',1); +INSERT INTO `migrations` VALUES (177,'2021_05_01_092523_add_only_run_when_server_online_option_to_schedules',1); +INSERT INTO `migrations` VALUES (178,'2021_05_03_201016_add_support_for_locking_a_backup',1); +INSERT INTO `migrations` VALUES (179,'2021_07_12_013420_remove_userinteraction',1); +INSERT INTO `migrations` VALUES (180,'2021_07_17_211512_create_user_ssh_keys_table',1); +INSERT INTO `migrations` VALUES (181,'2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table',1); +INSERT INTO `migrations` VALUES (182,'2021_08_21_175111_add_foreign_keys_to_mount_node_table',1); +INSERT INTO `migrations` VALUES (183,'2021_08_21_175118_add_foreign_keys_to_mount_server_table',1); +INSERT INTO `migrations` VALUES (184,'2021_08_21_180921_add_foreign_keys_to_egg_mount_table',1); +INSERT INTO `migrations` VALUES (185,'2022_01_25_030847_drop_google_analytics',1); +INSERT INTO `migrations` VALUES (186,'2022_05_07_165334_migrate_egg_images_array_to_new_format',1); +INSERT INTO `migrations` VALUES (187,'2022_05_28_135717_create_activity_logs_table',1); +INSERT INTO `migrations` VALUES (188,'2022_05_29_140349_create_activity_log_actors_table',1); +INSERT INTO `migrations` VALUES (189,'2022_06_18_112822_track_api_key_usage_for_activity_events',1); +INSERT INTO `migrations` VALUES (190,'2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table',1); +INSERT INTO `migrations` VALUES (191,'2022_08_16_230204_add_installed_at_column_to_servers_table',1); +INSERT INTO `migrations` VALUES (192,'2022_12_12_213937_update_mail_settings_to_new_format',1); +INSERT INTO `migrations` VALUES (193,'2023_01_24_210051_add_uuid_column_to_failed_jobs_table',1); +INSERT INTO `migrations` VALUES (194,'2023_02_23_191004_add_expires_at_column_to_api_keys_table',1); diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 1a0159ffe3..e585c7413a 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,4 +1,3 @@ -version: '3.8' x-common: database: &db-environment @@ -8,7 +7,7 @@ x-common: MYSQL_ROOT_PASSWORD: "CHANGE_ME_TOO" panel: &panel-environment - APP_URL: "https://example.com" + APP_URL: "http://example.com" # A list of valid timezones can be found here: http://php.net/manual/en/timezones.php APP_TIMEZONE: "UTC" APP_SERVICE_AUTHOR: "noreply@example.com" @@ -34,7 +33,7 @@ x-common: # services: database: - image: mariadb:10.5 + image: mariadb:11 restart: always command: --default-authentication-plugin=mysql_native_password volumes: @@ -71,6 +70,7 @@ services: REDIS_HOST: "cache" DB_HOST: "database" DB_PORT: "3306" + HASHIDS_LENGTH: 8 networks: default: ipam: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..1013226341 --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1738453229, + "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1739446958, + "narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2ff53fe64443980e139eaa286017f53f88336dd0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..bdb0cf9730 --- /dev/null +++ b/flake.nix @@ -0,0 +1,138 @@ +{ + description = "Pterodactyl Panel"; + + inputs = { + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default"; + }; + + outputs = {self, ...} @ inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} { + systems = import inputs.systems; + + perSystem = { + pkgs, + system, + ... + }: let + php = pkgs.php; # PHP 8.2 + + phpWithExtensions = php.buildEnv { + extensions = { + enabled, + all, + }: + enabled + ++ (with all; [ + redis + xdebug + ]); + extraConfig = '' + xdebug.mode=debug + ''; + }; + + composer = php.packages.composer.override {php = phpWithExtensions;}; + in { + # Initialize pkgs with our overlays + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + }; + + devShells.default = pkgs.mkShellNoCC { + buildInputs = with pkgs; [ + composer + nodejs_18 + nodePackages.pnpm + nodePackages.yarn + phpWithExtensions + ]; + + shellHook = '' + PATH="$PATH:${pkgs.docker-compose}/libexec/docker/cli-plugins" + ''; + }; + + packages.development = pkgs.dockerTools.buildImage { + name = "pterodactyl/development"; + tag = "panel"; + + copyToRoot = pkgs.buildEnv (let + caddyfile = pkgs.writeText "Caddyfile" '' + :80 { + root * /var/www/html/public/ + file_server + + header { + -Server + -X-Powered-By + Referrer-Policy "same-origin" + X-Frame-Options "deny" + X-XSS-Protection "1; mode=block" + X-Content-Type-Options "nosniff" + } + + encode gzip zstd + + php_fastcgi localhost:9000 { + trusted_proxies private_ranges + } + + try_files {path} {path}/ /index.php?{query} + } + ''; + + phpfpmConf = pkgs.writeText "php-fpm.conf" '' + [global] + error_log = /dev/stderr + daemonize = no + + [www] + user = nobody + group = nobody + + listen = 0.0.0.0:9000 + + pm = dynamic + pm.start_servers = 4 + pm.min_spare_servers = 4 + pm.max_spare_servers = 16 + pm.max_children = 64 + pm.max_requests = 256 + + clear_env = no + catch_workers_output = yes + + decorate_workers_output = no + ''; + in { + name = "image-root"; + paths = with pkgs; [ + (pkgs.runCommand "configs" {} '' + mkdir -p "$out"/etc/caddy + ln -s ${caddyfile} "$out"/etc/caddy/Caddyfile + ln -s ${phpfpmConf} "$out"/etc/php-fpm.conf + '') + bash + dockerTools.caCertificates + dockerTools.fakeNss + caddy + composer + coreutils + mysql80 + nodejs_18 + nodePackages.pnpm + nodePackages.yarn + phpWithExtensions + ]; + pathsToLink = ["/bin" "/etc"]; + }); + }; + }; + }; +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000..abb02f3860 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,28 @@ +const { pathsToModuleNameMapper } = require('ts-jest'); +const { compilerOptions } = require('./tsconfig'); + +/** @type {import('ts-jest').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + globals: { + 'ts-jest': { + isolatedModules: true, + }, + }, + moduleFileExtensions: ['js', 'ts', 'tsx', 'd.ts', 'json', 'node'], + moduleNameMapper: { + '\\.(jpe?g|png|gif|svg)$': '/resources/scripts/__mocks__/file.ts', + '\\.(s?css|less)$': 'identity-obj-proxy', + ...pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '/', + }), + }, + setupFilesAfterEnv: [ + '/resources/scripts/setup-tests.ts', + ], + transform: { + '.*\\.[t|j]sx$': 'babel-jest', + '.*\\.ts$': 'ts-jest', + }, + testPathIgnorePatterns: ['/node_modules/'], +}; diff --git a/package.json b/package.json index 1f3df5673e..732015ede2 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,59 @@ { "name": "pterodactyl-panel", + "engines": { + "node": ">=22" + }, "dependencies": { + "@floating-ui/react-dom-interactions": "^0.6.6", "@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.11", - "@tailwindcss/forms": "^0.2.1", - "axios": "^0.21.1", - "chart.js": "^2.8.0", + "@headlessui/react": "^1.6.4", + "@heroicons/react": "^1.0.6", + "@hot-loader/react-dom": "^16.14.0", + "@preact/signals-react": "^1.2.1", + "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/line-clamp": "^0.4.0", + "axios": "^1.13.2", + "boring-avatars": "^1.7.0", + "chart.js": "^3.8.0", + "classnames": "^2.3.1", "codemirror": "^5.57.0", - "date-fns": "^2.16.1", + "copy-to-clipboard": "^3.3.1", + "date-fns": "^2.28.0", "debounce": "^1.2.0", - "deepmerge": "^4.2.2", + "deepmerge-ts": "^4.2.1", "easy-peasy": "^4.0.1", "events": "^3.0.0", "formik": "^2.2.6", - "i18next": "^19.0.0", - "i18next-chained-backend": "^2.0.0", - "i18next-localstorage-backend": "^3.0.0", - "i18next-xhr-backend": "^3.2.2", + "framer-motion": "^6.3.10", + "i18next": "^25.8.0", + "i18next-http-backend": "^3.0.2", + "i18next-multiload-backend-adapter": "^2.3.0", + "pathe": "^2.0.3", "qrcode.react": "^1.0.1", - "query-string": "^6.7.0", - "react": "^16.13.1", - "react-copy-to-clipboard": "^5.0.2", + "react": "^16.14.0", + "react-chartjs-2": "^4.2.0", "react-dom": "npm:@hot-loader/react-dom", "react-fast-compare": "^3.2.0", - "react-google-recaptcha": "^2.0.1", "react-hot-loader": "^4.12.21", - "react-i18next": "^11.2.1", + "react-i18next": "^16.5.4", "react-router-dom": "^5.1.2", "react-transition-group": "^4.4.1", "reaptcha": "^1.7.2", "sockette": "^2.0.6", - "styled-components": "^5.2.1", + "styled-components": "^5.3.0", "styled-components-breakpoint": "^3.0.0-preview.20", "swr": "^0.2.3", - "tailwindcss": "^2.0.2", - "uuid": "^3.3.2", - "xterm": "^4.15.0", - "xterm-addon-attach": "^0.6.0", - "xterm-addon-fit": "^0.4.0", - "xterm-addon-search": "^0.7.0", + "tailwindcss": "^3.0.24", + "use-fit-text": "^2.4.0", + "uuid": "^13.0.0", + "xterm": "^4.19.0", + "xterm-addon-fit": "^0.5.0", + "xterm-addon-search": "^0.9.0", "xterm-addon-search-bar": "^0.2.0", - "xterm-addon-web-links": "^0.4.0", + "xterm-addon-unicode11": "^0.6.0", + "xterm-addon-web-links": "^0.6.0", "yup": "^0.29.1" }, "devDependencies": { @@ -51,73 +63,96 @@ "@babel/plugin-proposal-object-rest-spread": "^7.12.1", "@babel/plugin-proposal-optional-chaining": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.18.2", "@babel/plugin-transform-react-jsx": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.1", "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.1", "@babel/preset-typescript": "^7.12.1", "@babel/runtime": "^7.12.1", - "@types/chart.js": "^2.8.5", + "@fontsource-variable/ibm-plex-sans": "^5.2.8", + "@testing-library/dom": "^8.14.0", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "12.1.5", + "@testing-library/user-event": "^14.2.1", "@types/codemirror": "^0.0.98", "@types/debounce": "^1.2.0", "@types/events": "^3.0.0", - "@types/node": "^14.11.10", + "@types/jest": "^28.1.3", + "@types/node": "^22.0.0", + "@types/path-browserify": "^1.0.3", "@types/qrcode.react": "^1.0.1", - "@types/query-string": "^6.3.0", - "@types/react": "^16.9.41", + "@types/react": "^16.14.0", "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-dom": "^16.9.8", - "@types/react-helmet": "^6.0.0", + "@types/react-dom": "^16.9.16", "@types/react-redux": "^7.1.1", "@types/react-router": "^5.1.3", "@types/react-router-dom": "^5.1.3", "@types/react-transition-group": "^4.4.0", - "@types/styled-components": "^5.1.7", + "@types/styled-components": "5.1.7", "@types/uuid": "^3.4.5", - "@types/webpack-env": "^1.15.2", + "@types/webpack-env": "^1.18.8", "@types/yup": "^0.29.3", - "@typescript-eslint/eslint-plugin": "^4.25.0", - "@typescript-eslint/parser": "^4.25.0", - "autoprefixer": "^10.1.0", - "babel-loader": "^8.0.6", - "babel-plugin-styled-components": "^1.12.0", + "@typescript-eslint/eslint-plugin": "^5", + "@typescript-eslint/parser": "^5", + "autoprefixer": "^10.4.7", + "babel-jest": "^28.1.1", + "babel-loader": "^10.0.0", + "babel-plugin-styled-components": "^2.0.7", "cross-env": "^7.0.2", - "css-loader": "^3.2.1", - "eslint": "^7.27.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.23.3", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.23.2", - "eslint-plugin-react-hooks": "^4.2.0", - "fork-ts-checker-webpack-plugin": "^6.2.10", - "postcss": "^8.2.1", + "css-loader": "^7.1.2", + "eslint": "^8", + "eslint-config-prettier": "^8", + "eslint-plugin-jest-dom": "^4.0.2", + "eslint-plugin-prettier": "^4", + "eslint-plugin-react": "^7", + "eslint-plugin-react-hooks": "^4", + "identity-obj-proxy": "^3.0.0", + "jest": "^28.1.1", + "postcss": "^8.5.6", + "postcss-import": "^14.1.0", + "postcss-loader": "^8.2.0", + "postcss-nesting": "^13.0.2", + "postcss-preset-env": "^10.4.0", + "prettier": "^2.7.1", "redux-devtools-extension": "^2.13.8", - "source-map-loader": "^1.0.1", - "style-loader": "^1.2.1", - "svg-url-loader": "^6.0.0", - "terser-webpack-plugin": "^3.0.6", - "twin.macro": "^2.0.7", - "typescript": "^4.2.4", - "webpack": "^4.43.0", - "webpack-assets-manifest": "^3.1.1", - "webpack-bundle-analyzer": "^3.8.0", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.0", - "yarn-deduplicate": "^1.1.1" + "source-map-loader": "^5.0.0", + "style-loader": "^4.0.0", + "svg-url-loader": "^8.0.0", + "terser-webpack-plugin": "^5.3.14", + "ts-essentials": "^9.1.2", + "ts-jest": "^28.0.5", + "twin.macro": "^2.8.2", + "typescript": "~5.1.0", + "webpack": "^5.103.0", + "webpack-assets-manifest": "^6.4.0", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.2" }, "scripts": { "clean": "cd public/assets && find . \\( -name \"*.js\" -o -name \"*.map\" \\) -type f -delete", + "test": "jest", + "tsc": "tsc --noEmit", "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "serve": "yarn run clean && cross-env WEBPACK_PUBLIC_PATH=/webpack@hmr/ NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8080 --public https://pterodactyl.test --hot" + "serve": "yarn run clean && cross-env NODE_ENV=development USE_LOCAL_CERTS=true WEBPACK_PUBLIC_PATH=https://pterodactyl.test:5173/ webpack serve --progress --hot --server-type https" }, "browserslist": [ "> 0.5%", "last 2 versions", "firefox esr", "not dead" - ] + ], + "babelMacros": { + "twin": { + "preset": "styled-components" + }, + "styledComponents": { + "pure": true, + "displayName": true, + "fileName": true + } + } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000000..8a4bc1bb66 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,19 @@ +includes: + - ./vendor/larastan/larastan/extension.neon + - ./vendor/phpstan/phpstan-webmozart-assert/extension.neon + +rules: + - PHPStan\Rules\DeadCode\UnreachableStatementRule + - PHPStan\Rules\DeadCode\UnusedPrivateConstantRule + - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule + +parameters: + level: 4 + paths: + - app + ignoreErrors: + - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::allowed(\w+)\(\)#' + - '#Call to an undefined method Prologue\\Alerts\\AlertsMessageBag::(success|warning|danger|info)\(\)#' + excludePaths: + - app/Repositories + - app/Extensions/Spatie/Fractalistic/Fractal.php diff --git a/phpunit.dusk.xml b/phpunit.dusk.xml deleted file mode 100644 index 60392c9324..0000000000 --- a/phpunit.dusk.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - ./tests/Browser - - - - - ./app - - - diff --git a/phpunit.xml b/phpunit.xml index 227dadb9e7..7781d6f5df 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,41 +1,19 @@ - - - - ./app - - - - - ./tests/Browser/Processes - - - ./tests/Integration - - - ./tests/Unit - - - - - - - - - - - + + + + ./tests/Integration + + + ./tests/Unit + + + + + + + + ./app + + diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..0cc56abe40 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,17 @@ +module.exports = { + plugins: [ + require('postcss-import'), + // We want to make use of nesting following the CSS Nesting spec, and not the + // SASS style nesting. + // + // @see https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting + require('tailwindcss/nesting')(require('postcss-nesting')), + require('tailwindcss'), + require('autoprefixer'), + require('postcss-preset-env')({ + features: { + 'nesting-rules': false, + }, + }), + ], +}; diff --git a/public/.gitignore b/public/.gitignore index 31c19f1c1d..477f0a1623 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -1,3 +1,4 @@ +assets assets/* !assets/svgs !assets/svgs/*.svg diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index 8e3580f2be..f9b5fd0edc 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -833,3 +833,23 @@ pre { background-color: #515f6cbb; border-color: #1f2933; } + +/* Make the row a flex container so all columns in the same row can have equal height */ +.equal-height { + display: flex; + flex-wrap: wrap; +} + +.equal-height>[class*="col-"] { + display: flex; +} + +.equal-height .box { + display: flex; + flex-direction: column; + width: 100%; +} + +.equal-height .box-body { + flex: 1; +} diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 39818963d7..1437c04e2a 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -82,12 +82,13 @@ $('#pEggId').on('change', function (event) { let parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); let objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); - const images = _.get(objectChain, 'docker_images', []) + const images = _.get(objectChain, 'docker_images', {}) $('#pDefaultContainer').html(''); - for (let i = 0; i < images.length; i++) { + const keys = Object.keys(images); + for (let i = 0; i < keys.length; i++) { let opt = document.createElement('option'); - opt.value = images[i]; - opt.innerHTML = images[i]; + opt.value = images[keys[i]]; + opt.innerText = keys[i] + " (" + images[keys[i]] + ")"; $('#pDefaultContainer').append(opt); } @@ -108,6 +109,12 @@ $('#pEggId').on('change', function (event) { ), }); + function escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + const variableIds = {}; $('#appendVariablesTo').html(''); $.each(_.get(objectChain, 'variables', []), function (i, item) { @@ -116,11 +123,11 @@ $('#pEggId').on('change', function (event) { let isRequired = (item.required === 1) ? 'Required ' : ''; let dataAppend = ' \
\ - \ - \ -

' + item.description + '
\ - Access in Startup: {{' + item.env_variable + '}}
\ - Validation Rules: ' + item.rules + '

\ + \ + \ +

' + escapeHtml(item.description) + '
\ + Access in Startup: {{' + escapeHtml(item.env_variable) + '}}
\ + Validation Rules: ' + escapeHtml(item.rules) + '

\
\ '; $('#appendVariablesTo').append(dataAppend); diff --git a/public/themes/pterodactyl/js/admin/server/transfer.js b/public/themes/pterodactyl/js/admin/server/transfer.js index 5c2664a862..6f12a4efaa 100644 --- a/public/themes/pterodactyl/js/admin/server/transfer.js +++ b/public/themes/pterodactyl/js/admin/server/transfer.js @@ -1,25 +1,32 @@ $(document).ready(function () { + const $modal = $('#transferServerModal'); + $('#pNodeId').select2({ placeholder: 'Select a Node', + dropdownParent: $modal, }).change(); $('#pAllocation').select2({ placeholder: 'Select a Default Allocation', + dropdownParent: $modal, }); $('#pAllocationAdditional').select2({ placeholder: 'Select Additional Allocations', + dropdownParent: $modal, }); }); $('#pNodeId').on('change', function () { let currentNode = $(this).val(); + const $modal = $('#transferServerModal'); $.each(Pterodactyl.nodeData, function (i, v) { if (v.id == currentNode) { $('#pAllocation').html('').select2({ data: v.allocations, placeholder: 'Select a Default Allocation', + dropdownParent: $modal, }); updateAdditionalAllocations(); @@ -34,6 +41,7 @@ $('#pAllocation').on('change', function () { function updateAdditionalAllocations() { let currentAllocation = $('#pAllocation').val(); let currentNode = $('#pNodeId').val(); + const $modal = $('#transferServerModal'); $.each(Pterodactyl.nodeData, function (i, v) { if (v.id == currentNode) { @@ -50,6 +58,7 @@ function updateAdditionalAllocations() { $('#pAllocationAdditional').html('').select2({ data: allocations, placeholder: 'Select Additional Allocations', + dropdownParent: $modal, }); } }); diff --git a/resources/lang/en/activity.php b/resources/lang/en/activity.php new file mode 100644 index 0000000000..f192626974 --- /dev/null +++ b/resources/lang/en/activity.php @@ -0,0 +1,133 @@ + [ + 'fail' => 'Failed log in', + 'success' => 'Logged in', + 'password-reset' => 'Password reset', + 'reset-password' => 'Requested password reset', + 'checkpoint' => 'Two-factor authentication requested', + 'recovery-token' => 'Used two-factor recovery token', + 'token' => 'Solved two-factor challenge', + 'ip-blocked' => 'Blocked request from unlisted IP address for :identifier', + 'sftp' => [ + 'fail' => 'Failed SFTP log in', + ], + ], + 'user' => [ + 'user' => [ + 'create' => 'Created a new user :email', + ], + 'account' => [ + 'email-changed' => 'Changed email from :old to :new', + 'password-changed' => 'Changed password', + ], + 'api-key' => [ + 'create' => 'Created new API key :identifier', + 'delete' => 'Deleted API key :identifier', + ], + 'ssh-key' => [ + 'create' => 'Added SSH key :fingerprint to account', + 'delete' => 'Removed SSH key :fingerprint from account', + ], + 'two-factor' => [ + 'create' => 'Enabled two-factor auth', + 'delete' => 'Disabled two-factor auth', + ], + ], + 'server' => [ + 'reinstall' => 'Reinstalled server', + 'console' => [ + 'command' => 'Executed ":command" on the server', + ], + 'power' => [ + 'start' => 'Started the server', + 'stop' => 'Stopped the server', + 'restart' => 'Restarted the server', + 'kill' => 'Killed the server process', + ], + 'backup' => [ + 'download' => 'Downloaded the :name backup', + 'delete' => 'Deleted the :name backup', + 'restore' => 'Restored the :name backup (deleted files: :truncate)', + 'restore-complete' => 'Completed restoration of the :name backup', + 'restore-failed' => 'Failed to complete restoration of the :name backup', + 'start' => 'Started a new backup :name', + 'complete' => 'Marked the :name backup as complete', + 'fail' => 'Marked the :name backup as failed', + 'lock' => 'Locked the :name backup', + 'unlock' => 'Unlocked the :name backup', + ], + 'database' => [ + 'create' => 'Created new database :name', + 'rotate-password' => 'Password rotated for database :name', + 'delete' => 'Deleted database :name', + ], + 'file' => [ + 'compress_one' => 'Compressed :directory:file', + 'compress_other' => 'Compressed :count files in :directory', + 'read' => 'Viewed the contents of :file', + 'copy' => 'Created a copy of :file', + 'create-directory' => 'Created directory :directory:name', + 'decompress' => 'Decompressed :files in :directory', + 'delete_one' => 'Deleted :directory:files.0', + 'delete_other' => 'Deleted :count files in :directory', + 'download' => 'Downloaded :file', + 'pull' => 'Downloaded a remote file from :url to :directory', + 'rename_one' => 'Renamed :directory:files.0.from to :directory:files.0.to', + 'rename_other' => 'Renamed :count files in :directory', + 'write' => 'Wrote new content to :file', + 'upload' => 'Began a file upload', + 'uploaded' => 'Uploaded :directory:file', + ], + 'sftp' => [ + 'denied' => 'Blocked SFTP access due to permissions', + 'create_one' => 'Created :files.0', + 'create_other' => 'Created :count new files', + 'write_one' => 'Modified the contents of :files.0', + 'write_other' => 'Modified the contents of :count files', + 'delete_one' => 'Deleted :files.0', + 'delete_other' => 'Deleted :count files', + 'create-directory_one' => 'Created the :files.0 directory', + 'create-directory_other' => 'Created :count directories', + 'rename_one' => 'Renamed :files.0.from to :files.0.to', + 'rename_other' => 'Renamed or moved :count files', + ], + 'allocation' => [ + 'create' => 'Added :allocation to the server', + 'notes' => 'Updated the notes for :allocation from ":old" to ":new"', + 'primary' => 'Set :allocation as the primary server allocation', + 'delete' => 'Deleted the :allocation allocation', + ], + 'schedule' => [ + 'create' => 'Created the :name schedule', + 'update' => 'Updated the :name schedule', + 'execute' => 'Manually executed the :name schedule', + 'delete' => 'Deleted the :name schedule', + ], + 'task' => [ + 'create' => 'Created a new ":action" task for the :name schedule', + 'update' => 'Updated the ":action" task for the :name schedule', + 'delete' => 'Deleted a task for the :name schedule', + ], + 'settings' => [ + 'rename' => 'Renamed the server from :old to :new', + 'description' => 'Changed the server description from :old to :new', + ], + 'startup' => [ + 'edit' => 'Changed the :variable variable from ":old" to ":new"', + 'image' => 'Updated the Docker Image for the server from :old to :new', + ], + 'subuser' => [ + 'create' => 'Added :email as a subuser', + 'update' => 'Updated the subuser permissions for :email', + 'delete' => 'Removed :email as a subuser', + ], + ], +]; diff --git a/resources/lang/en/admin/nests.php b/resources/lang/en/admin/nests.php index 19e6b5a1e8..2a6f377077 100644 --- a/resources/lang/en/admin/nests.php +++ b/resources/lang/en/admin/nests.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'notices' => [ diff --git a/resources/lang/en/admin/node.php b/resources/lang/en/admin/node.php index 5d59e41ce9..c81b412a43 100644 --- a/resources/lang/en/admin/node.php +++ b/resources/lang/en/admin/node.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'validation' => [ @@ -16,7 +9,7 @@ 'allocations_added' => 'Allocations have successfully been added to this node.', 'node_deleted' => 'Node has been successfully removed from the panel.', 'location_required' => 'You must have at least one location configured before you can add a node to this panel.', - 'node_created' => 'Successfully created new node. You can automatically configure the daemon on this machine by visiting the \'Configuration\' tab. Before you can add any servers you must first allocate at least one IP address and port.', + 'node_created' => 'Successfully created new node. You can automatically configure the daemon on this machine by visiting the \'Configuration\' tab. Before you can add any servers you must first allocate at least one IP address and port.', 'node_updated' => 'Node information has been updated. If any daemon settings were changed you will need to reboot it for those changes to take effect.', 'unallocated_deleted' => 'Deleted all un-allocated ports for :ip.', ], diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php index 6e3ce9f942..29cb72d183 100644 --- a/resources/lang/en/admin/server.php +++ b/resources/lang/en/admin/server.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'exceptions' => [ diff --git a/resources/lang/en/admin/user.php b/resources/lang/en/admin/user.php index b7f756cb4f..338d0e761f 100644 --- a/resources/lang/en/admin/user.php +++ b/resources/lang/en/admin/user.php @@ -1,14 +1,8 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'exceptions' => [ + 'delete_self' => 'You cannot delete your own account.', 'user_has_servers' => 'Cannot delete a user with active servers attached to their account. Please delete their servers before continuing.', ], 'notices' => [ diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php new file mode 100644 index 0000000000..a4a3aafdf1 --- /dev/null +++ b/resources/lang/en/command/messages.php @@ -0,0 +1,66 @@ + [ + 'no_location_found' => 'Could not locate a record matching the provided short code.', + 'ask_short' => 'Location Short Code', + 'ask_long' => 'Location Description', + 'created' => 'Successfully created a new location (:name) with an ID of :id.', + 'deleted' => 'Successfully deleted the requested location.', + ], + 'user' => [ + 'search_users' => 'Enter a Username, User ID, or Email Address', + 'select_search_user' => 'ID of user to delete (Enter \'0\' to re-search)', + 'deleted' => 'User successfully deleted from the Panel.', + 'confirm_delete' => 'Are you sure you want to delete this user from the Panel?', + 'no_users_found' => 'No users were found for the search term provided.', + 'multiple_found' => 'Multiple accounts were found for the user provided, unable to delete a user because of the --no-interaction flag.', + 'ask_admin' => 'Is this user an administrator?', + 'ask_email' => 'Email Address', + 'ask_username' => 'Username', + 'ask_name_first' => 'First Name', + 'ask_name_last' => 'Last Name', + 'ask_password' => 'Password', + 'ask_password_tip' => 'If you would like to create an account with a random password emailed to the user, re-run this command (CTRL+C) and pass the `--no-password` flag.', + 'ask_password_help' => 'Passwords must be at least 8 characters in length and contain at least one capital letter and number.', + '2fa_help_text' => [ + 'This command will disable 2-factor authentication for a user\'s account if it is enabled. This should only be used as an account recovery command if the user is locked out of their account.', + 'If this is not what you wanted to do, press CTRL+C to exit this process.', + ], + '2fa_disabled' => '2-Factor authentication has been disabled for :email.', + ], + 'schedule' => [ + 'output_line' => 'Dispatching job for first task in `:schedule` (:hash).', + ], + 'maintenance' => [ + 'deleting_service_backup' => 'Deleting service backup file :file.', + ], + 'server' => [ + 'rebuild_failed' => 'Rebuild request for ":name" (#:id) on node ":node" failed with error: :message', + 'reinstall' => [ + 'failed' => 'Reinstall request for ":name" (#:id) on node ":node" failed with error: :message', + 'confirm' => 'You are about to reinstall against a group of servers. Do you wish to continue?', + ], + 'power' => [ + 'confirm' => 'You are about to perform a :action against :count servers. Do you wish to continue?', + 'action_failed' => 'Power action request for ":name" (#:id) on node ":node" failed with error: :message', + ], + ], + 'environment' => [ + 'mail' => [ + 'ask_smtp_host' => 'SMTP Host (e.g. smtp.gmail.com)', + 'ask_smtp_port' => 'SMTP Port', + 'ask_smtp_username' => 'SMTP Username', + 'ask_smtp_password' => 'SMTP Password', + 'ask_mailgun_domain' => 'Mailgun Domain', + 'ask_mailgun_endpoint' => 'Mailgun Endpoint', + 'ask_mailgun_secret' => 'Mailgun Secret', + 'ask_mandrill_secret' => 'Mandrill Secret', + 'ask_postmark_username' => 'Postmark API Key', + 'ask_driver' => 'Which driver should be used for sending emails?', + 'ask_mail_from' => 'Email address emails should originate from', + 'ask_mail_name' => 'Name that emails should appear from', + 'ask_encryption' => 'Encryption method to use', + ], + ], +]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index 4d44c4ff94..cc2f2044be 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -4,7 +4,7 @@ 'daemon_connection_failed' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', 'node' => [ 'servers_attached' => 'A node must have no servers linked to it in order to be deleted.', - 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (config.yml) for the daemon to apply these changes.', + 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (config.yml) for the daemon to apply these changes.', ], 'allocations' => [ 'server_using' => 'A server is currently assigned to this allocation. An allocation can only be deleted if no server is currently assigned.', diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx index 5e5777fee6..040097eaa8 100644 --- a/resources/scripts/TransitionRouter.tsx +++ b/resources/scripts/TransitionRouter.tsx @@ -7,7 +7,7 @@ import tw from 'twin.macro'; const StyledSwitchTransition = styled(SwitchTransition)` ${tw`relative`}; - + & section { ${tw`absolute w-full top-0 left-0`}; } @@ -19,9 +19,7 @@ const TransitionRouter: React.FC = ({ children }) => { render={({ location }) => ( -
- {children} -
+
{children}
)} diff --git a/resources/scripts/__mocks__/file.ts b/resources/scripts/__mocks__/file.ts new file mode 100644 index 0000000000..86059f3629 --- /dev/null +++ b/resources/scripts/__mocks__/file.ts @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; diff --git a/resources/scripts/api/account/activity.ts b/resources/scripts/api/account/activity.ts new file mode 100644 index 0000000000..eef215696a --- /dev/null +++ b/resources/scripts/api/account/activity.ts @@ -0,0 +1,33 @@ +import useSWR, { ConfigInterface, responseInterface } from 'swr'; +import { ActivityLog, Transformers } from '@definitions/user'; +import { AxiosError } from 'axios'; +import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { toPaginatedSet } from '@definitions/helpers'; +import useFilteredObject from '@/plugins/useFilteredObject'; +import { useUserSWRKey } from '@/plugins/useSWRKey'; + +export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'>; + +const useActivityLogs = ( + filters?: ActivityLogFilters, + config?: ConfigInterface, AxiosError> +): responseInterface, AxiosError> => { + const key = useUserSWRKey(['account', 'activity', JSON.stringify(useFilteredObject(filters || {}))]); + + return useSWR>( + key, + async () => { + const { data } = await http.get('/api/client/account/activity', { + params: { + ...withQueryBuilderParams(filters), + include: ['actor'], + }, + }); + + return toPaginatedSet(data, Transformers.toActivityLog); + }, + { revalidateOnMount: false, ...(config || {}) } + ); +}; + +export { useActivityLogs }; diff --git a/resources/scripts/api/account/createApiKey.ts b/resources/scripts/api/account/createApiKey.ts index 7067ec145e..b20760e62e 100644 --- a/resources/scripts/api/account/createApiKey.ts +++ b/resources/scripts/api/account/createApiKey.ts @@ -7,11 +7,13 @@ export default (description: string, allowedIps: string): Promise 0 ? allowedIps.split('\n') : [], }) - .then(({ data }) => resolve({ - ...rawDataToApiKey(data.attributes), - // eslint-disable-next-line camelcase - secretToken: data.meta?.secret_token ?? '', - })) + .then(({ data }) => + resolve({ + ...rawDataToApiKey(data.attributes), + // eslint-disable-next-line camelcase + secretToken: data.meta?.secret_token ?? '', + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/account/disableAccountTwoFactor.ts b/resources/scripts/api/account/disableAccountTwoFactor.ts index 2b41fe20fe..fe8064008e 100644 --- a/resources/scripts/api/account/disableAccountTwoFactor.ts +++ b/resources/scripts/api/account/disableAccountTwoFactor.ts @@ -1,9 +1,11 @@ import http from '@/api/http'; -export default (password: string): Promise => { +function disableAccountTwoFactor(password: string): Promise { return new Promise((resolve, reject) => { - http.delete('/api/client/account/two-factor', { params: { password } }) + http.post('/api/client/account/two-factor/disable', { password }) .then(() => resolve()) .catch(reject); }); -}; +} + +export default disableAccountTwoFactor; diff --git a/resources/scripts/api/account/enableAccountTwoFactor.ts b/resources/scripts/api/account/enableAccountTwoFactor.ts index e7a15f62de..25c63ad477 100644 --- a/resources/scripts/api/account/enableAccountTwoFactor.ts +++ b/resources/scripts/api/account/enableAccountTwoFactor.ts @@ -1,7 +1,7 @@ import http from '@/api/http'; -export default async (code: string): Promise => { - const { data } = await http.post('/api/client/account/two-factor', { code }); +export default async (code: string, password: string): Promise => { + const { data } = await http.post('/api/client/account/two-factor', { code, password }); return data.attributes.tokens; }; diff --git a/resources/scripts/api/account/ssh-keys.ts b/resources/scripts/api/account/ssh-keys.ts new file mode 100644 index 0000000000..f35ff1e12f --- /dev/null +++ b/resources/scripts/api/account/ssh-keys.ts @@ -0,0 +1,32 @@ +import useSWR, { ConfigInterface } from 'swr'; +import http, { FractalResponseList } from '@/api/http'; +import { SSHKey, Transformers } from '@definitions/user'; +import { AxiosError } from 'axios'; +import { useUserSWRKey } from '@/plugins/useSWRKey'; + +const useSSHKeys = (config?: ConfigInterface) => { + const key = useUserSWRKey(['account', 'ssh-keys']); + + return useSWR( + key, + async () => { + const { data } = await http.get('/api/client/account/ssh-keys'); + + return (data as FractalResponseList).data.map((datum: any) => { + return Transformers.toSSHKey(datum.attributes); + }); + }, + { revalidateOnMount: false, ...(config || {}) } + ); +}; + +const createSSHKey = async (name: string, publicKey: string): Promise => { + const { data } = await http.post('/api/client/account/ssh-keys', { name, public_key: publicKey }); + + return Transformers.toSSHKey(data.attributes); +}; + +const deleteSSHKey = async (fingerprint: string): Promise => + await http.post('/api/client/account/ssh-keys/remove', { fingerprint }); + +export { useSSHKeys, createSSHKey, deleteSSHKey }; diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index af2f5faa38..ff0de7ac62 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -14,12 +14,15 @@ export interface LoginData { export default ({ username, password, recaptchaData }: LoginData): Promise => { return new Promise((resolve, reject) => { - http.post('/auth/login', { - user: username, - password, - 'g-recaptcha-response': recaptchaData, - }) - .then(response => { + http.get('/sanctum/csrf-cookie') + .then(() => + http.post('/auth/login', { + user: username, + password, + 'g-recaptcha-response': recaptchaData, + }) + ) + .then((response) => { if (!(response.data instanceof Object)) { return reject(new Error('An error occurred while processing the login request.')); } diff --git a/resources/scripts/api/auth/loginCheckpoint.ts b/resources/scripts/api/auth/loginCheckpoint.ts index 2d139fa527..73ffb21114 100644 --- a/resources/scripts/api/auth/loginCheckpoint.ts +++ b/resources/scripts/api/auth/loginCheckpoint.ts @@ -6,12 +6,14 @@ export default (token: string, code: string, recoveryToken?: string): Promise 0) ? recoveryToken : undefined, + recovery_token: recoveryToken && recoveryToken.length > 0 ? recoveryToken : undefined, }) - .then(response => resolve({ - complete: response.data.data.complete, - intended: response.data.data.intended || undefined, - })) + .then((response) => + resolve({ + complete: response.data.data.complete, + intended: response.data.data.intended || undefined, + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/auth/performPasswordReset.ts b/resources/scripts/api/auth/performPasswordReset.ts index 6695099eee..0dd12f4701 100644 --- a/resources/scripts/api/auth/performPasswordReset.ts +++ b/resources/scripts/api/auth/performPasswordReset.ts @@ -19,10 +19,12 @@ export default (email: string, data: Data): Promise => { password: data.password, password_confirmation: data.passwordConfirmation, }) - .then(response => resolve({ - redirectTo: response.data.redirect_to, - sendToLogin: response.data.send_to_login, - })) + .then((response) => + resolve({ + redirectTo: response.data.redirect_to, + sendToLogin: response.data.send_to_login, + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/auth/requestPasswordResetEmail.ts b/resources/scripts/api/auth/requestPasswordResetEmail.ts index 2168160c2e..d68fa44472 100644 --- a/resources/scripts/api/auth/requestPasswordResetEmail.ts +++ b/resources/scripts/api/auth/requestPasswordResetEmail.ts @@ -3,7 +3,7 @@ import http from '@/api/http'; export default (email: string, recaptchaData?: string): Promise => { return new Promise((resolve, reject) => { http.post('/auth/password', { email, 'g-recaptcha-response': recaptchaData }) - .then(response => resolve(response.data.status || '')) + .then((response) => resolve(response.data.status || '')) .catch(reject); }); }; diff --git a/resources/scripts/api/definitions/helpers.ts b/resources/scripts/api/definitions/helpers.ts new file mode 100644 index 0000000000..eeb933f5a4 --- /dev/null +++ b/resources/scripts/api/definitions/helpers.ts @@ -0,0 +1,55 @@ +import { + FractalPaginatedResponse, + FractalResponseData, + FractalResponseList, + getPaginationSet, + PaginatedResult, +} from '@/api/http'; +import { Model } from '@definitions/index'; + +type TransformerFunc = (callback: FractalResponseData) => T; + +const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list'; + +function transform(data: null | undefined, transformer: TransformerFunc, missing?: M): M; +function transform( + data: FractalResponseData | null | undefined, + transformer: TransformerFunc, + missing?: M +): T | M; +function transform( + data: FractalResponseList | FractalPaginatedResponse | null | undefined, + transformer: TransformerFunc, + missing?: M +): T[] | M; +function transform( + data: FractalResponseData | FractalResponseList | FractalPaginatedResponse | null | undefined, + transformer: TransformerFunc, + missing = undefined +) { + if (data === undefined || data === null) { + return missing; + } + + if (isList(data)) { + return data.data.map(transformer); + } + + if (!data || !data.attributes || data.object === 'null_resource') { + return missing; + } + + return transformer(data); +} + +function toPaginatedSet>( + response: FractalPaginatedResponse, + transformer: T +): PaginatedResult> { + return { + items: transform(response, transformer) as ReturnType[], + pagination: getPaginationSet(response.meta.pagination), + }; +} + +export { transform, toPaginatedSet }; diff --git a/resources/scripts/api/definitions/index.d.ts b/resources/scripts/api/definitions/index.d.ts new file mode 100644 index 0000000000..fe3026daba --- /dev/null +++ b/resources/scripts/api/definitions/index.d.ts @@ -0,0 +1,34 @@ +import { MarkRequired } from 'ts-essentials'; +import { FractalResponseData, FractalResponseList } from '../http'; + +export type UUID = string; +export type Identifier

= `${P}_${string}`; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Model {} + +interface ModelWithRelationships extends Model { + relationships: Record; +} + +/** + * Allows a model to have optional relationships that are marked as being + * present in a given pathway. This allows different API calls to specify the + * "completeness" of a response object without having to make every API return + * the same information, or every piece of logic do explicit null checking. + * + * Example: + * >> const user: WithLoaded = {}; + * >> // "user.servers" is no longer potentially undefined. + */ +type WithLoaded = M & { + relationships: MarkRequired; +}; + +/** + * Helper type that allows you to infer the type of an object by giving + * it the specific API request function with a return type. For example: + * + * type Egg = InferModel; + */ +export type InferModel any> = ReturnType extends Promise ? U : T; diff --git a/resources/scripts/api/definitions/user/index.ts b/resources/scripts/api/definitions/user/index.ts new file mode 100644 index 0000000000..7d8db7094e --- /dev/null +++ b/resources/scripts/api/definitions/user/index.ts @@ -0,0 +1,2 @@ +export * from './models.d'; +export { default as Transformers, MetaTransformers } from './transformers'; diff --git a/resources/scripts/api/definitions/user/models.d.ts b/resources/scripts/api/definitions/user/models.d.ts new file mode 100644 index 0000000000..a5b40ca678 --- /dev/null +++ b/resources/scripts/api/definitions/user/models.d.ts @@ -0,0 +1,35 @@ +import { Model, UUID } from '@/api/definitions'; +import { SubuserPermission } from '@/state/server/subusers'; + +interface User extends Model { + uuid: string; + username: string; + email: string; + image: string; + twoFactorEnabled: boolean; + createdAt: Date; + permissions: SubuserPermission[]; + can(permission: SubuserPermission): boolean; +} + +interface SSHKey extends Model { + name: string; + publicKey: string; + fingerprint: string; + createdAt: Date; +} + +interface ActivityLog extends Model<'actor'> { + id: string; + batch: UUID | null; + event: string; + ip: string | null; + isApi: boolean; + description: string | null; + properties: Record; + hasAdditionalMetadata: boolean; + timestamp: Date; + relationships: { + actor: User | null; + }; +} diff --git a/resources/scripts/api/definitions/user/transformers.ts b/resources/scripts/api/definitions/user/transformers.ts new file mode 100644 index 0000000000..1fa62d3f9a --- /dev/null +++ b/resources/scripts/api/definitions/user/transformers.ts @@ -0,0 +1,50 @@ +import * as Models from '@definitions/user/models'; +import { FractalResponseData } from '@/api/http'; +import { transform } from '@definitions/helpers'; + +export default class Transformers { + static toSSHKey = (data: Record): Models.SSHKey => { + return { + name: data.name, + publicKey: data.public_key, + fingerprint: data.fingerprint, + createdAt: new Date(data.created_at), + }; + }; + + static toUser = ({ attributes }: FractalResponseData): Models.User => { + return { + uuid: attributes.uuid, + username: attributes.username, + email: attributes.email, + image: attributes.image, + twoFactorEnabled: attributes['2fa_enabled'], + permissions: attributes.permissions || [], + createdAt: new Date(attributes.created_at), + can(permission): boolean { + return this.permissions.includes(permission); + }, + }; + }; + + static toActivityLog = ({ attributes }: FractalResponseData): Models.ActivityLog => { + const { actor } = attributes.relationships || {}; + + return { + id: attributes.id, + batch: attributes.batch, + event: attributes.event, + ip: attributes.ip, + isApi: attributes.is_api, + description: attributes.description, + properties: attributes.properties, + hasAdditionalMetadata: attributes.has_additional_metadata ?? false, + timestamp: new Date(attributes.timestamp), + relationships: { + actor: transform(actor as FractalResponseData, this.toUser, null), + }, + }; + }; +} + +export class MetaTransformers {} diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts index 6094b1706e..4e29f1532c 100644 --- a/resources/scripts/api/getServers.ts +++ b/resources/scripts/api/getServers.ts @@ -15,10 +15,12 @@ export default ({ query, ...params }: QueryParams): Promise resolve({ - items: (data.data || []).map((datum: any) => rawDataToServerObject(datum)), - pagination: getPaginationSet(data.meta.pagination), - })) + .then(({ data }) => + resolve({ + items: (data.data || []).map((datum: any) => rawDataToServerObject(datum)), + pagination: getPaginationSet(data.meta.pagination), + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index d9f64ede2a..382c1119a1 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -2,6 +2,7 @@ import axios, { AxiosInstance } from 'axios'; import { store } from '@/state'; const http: AxiosInstance = axios.create({ + withCredentials: true, timeout: 20000, headers: { 'X-Requested-With': 'XMLHttpRequest', @@ -10,49 +11,28 @@ const http: AxiosInstance = axios.create({ }, }); -http.interceptors.request.use(req => { - const cookies = document.cookie.split(';').reduce((obj, val) => { - const [ key, value ] = val.trim().split('=').map(decodeURIComponent); - - return { ...obj, [key]: value }; - }, {} as Record); - - req.headers['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'] || 'nil'; - - return req; -}); - -http.interceptors.request.use(req => { - if (!req.url?.endsWith('/resources') && (req.url?.indexOf('_debugbar') || -1) < 0) { +http.interceptors.request.use((req) => { + if (!req.url?.endsWith('/resources')) { store.getActions().progress.startContinuous(); } return req; }); -http.interceptors.response.use(resp => { - if (!resp.request?.url?.endsWith('/resources') && (resp.request?.url?.indexOf('_debugbar') || -1) < 0) { - store.getActions().progress.setComplete(); - } - - return resp; -}, error => { - store.getActions().progress.setComplete(); - - throw error; -}); +http.interceptors.response.use( + (resp) => { + if (!resp.request?.url?.endsWith('/resources')) { + store.getActions().progress.setComplete(); + } -// If we have a phpdebugbar instance registered at this point in time go -// ahead and route the response data through to it so things show up. -// @ts-ignore -if (typeof window.phpdebugbar !== 'undefined') { - http.interceptors.response.use(response => { - // @ts-ignore - window.phpdebugbar.ajaxHandler.handle(response.request); + return resp; + }, + (error) => { + store.getActions().progress.setComplete(); - return response; - }); -} + throw error; + } +); export default http; @@ -60,7 +40,7 @@ export default http; * Converts an error into a human readable response. Mostly just a generic helper to * make sure we display the message from the server back to the user if we can. */ -export function httpErrorToHuman (error: any): string { +export function httpErrorToHuman(error: any): string { if (error.response && error.response.data) { let { data } = error.response; @@ -91,7 +71,7 @@ export interface FractalResponseData { object: string; attributes: { [k: string]: any; - relationships?: Record; + relationships?: Record; }; } @@ -100,12 +80,26 @@ export interface FractalResponseList { data: FractalResponseData[]; } +export interface FractalPaginatedResponse extends FractalResponseList { + meta: { + pagination: { + total: number; + count: number; + /* eslint-disable camelcase */ + per_page: number; + current_page: number; + total_pages: number; + /* eslint-enable camelcase */ + }; + }; +} + export interface PaginatedResult { items: T[]; pagination: PaginationDataSet; } -interface PaginationDataSet { +export interface PaginationDataSet { total: number; count: number; perPage: number; @@ -113,7 +107,7 @@ interface PaginationDataSet { totalPages: number; } -export function getPaginationSet (data: any): PaginationDataSet { +export function getPaginationSet(data: any): PaginationDataSet { return { total: data.total, count: data.count, @@ -122,3 +116,45 @@ export function getPaginationSet (data: any): PaginationDataSet { totalPages: data.total_pages, }; } + +type QueryBuilderFilterValue = string | number | boolean | null; + +export interface QueryBuilderParams { + page?: number; + filters?: { + [K in FilterKeys]?: QueryBuilderFilterValue | Readonly; + }; + sorts?: { + [K in SortKeys]?: -1 | 0 | 1 | 'asc' | 'desc' | null; + }; +} + +/** + * Helper function that parses a data object provided and builds query parameters + * for the Laravel Query Builder package automatically. This will apply sorts and + * filters deterministically based on the provided values. + */ +export const withQueryBuilderParams = (data?: QueryBuilderParams): Record => { + if (!data) return {}; + + const filters = Object.keys(data.filters || {}).reduce((obj, key) => { + const value = data.filters?.[key]; + + return !value || value === '' ? obj : { ...obj, [`filter[${key}]`]: value }; + }, {} as NonNullable); + + const sorts = Object.keys(data.sorts || {}).reduce((arr, key) => { + const value = data.sorts?.[key]; + if (!value || !['asc', 'desc', 1, -1].includes(value)) { + return arr; + } + + return [...arr, (value === -1 || value === 'desc' ? '-' : '') + key]; + }, [] as string[]); + + return { + ...filters, + sort: !sorts.length ? undefined : sorts.join(','), + page: data.page, + }; +}; diff --git a/resources/scripts/api/interceptors.ts b/resources/scripts/api/interceptors.ts index 8ff03fbf0b..7b8ac87dae 100644 --- a/resources/scripts/api/interceptors.ts +++ b/resources/scripts/api/interceptors.ts @@ -3,14 +3,19 @@ import { AxiosError } from 'axios'; import { History } from 'history'; export const setupInterceptors = (history: History) => { - http.interceptors.response.use(resp => resp, (error: AxiosError) => { - if (error.response?.status === 400) { - if (error.response?.data.errors?.[0].code === 'TwoFactorAuthRequiredException') { - if (!window.location.pathname.startsWith('/account')) { - history.replace('/account', { twoFactorRedirect: true }); + http.interceptors.response.use( + (resp) => resp, + (error: AxiosError) => { + if (error.response?.status === 400) { + if ( + (error.response?.data as Record).errors?.[0].code === 'TwoFactorAuthRequiredException' + ) { + if (!window.location.pathname.startsWith('/account')) { + history.replace('/account', { twoFactorRedirect: true }); + } } } + throw error; } - throw error; - }); + ); }; diff --git a/resources/scripts/api/server/activity.ts b/resources/scripts/api/server/activity.ts new file mode 100644 index 0000000000..a7fa8d31bf --- /dev/null +++ b/resources/scripts/api/server/activity.ts @@ -0,0 +1,35 @@ +import useSWR, { ConfigInterface, responseInterface } from 'swr'; +import { ActivityLog, Transformers } from '@definitions/user'; +import { AxiosError } from 'axios'; +import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { toPaginatedSet } from '@definitions/helpers'; +import useFilteredObject from '@/plugins/useFilteredObject'; +import { useServerSWRKey } from '@/plugins/useSWRKey'; +import { ServerContext } from '@/state/server'; + +export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'>; + +const useActivityLogs = ( + filters?: ActivityLogFilters, + config?: ConfigInterface, AxiosError> +): responseInterface, AxiosError> => { + const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); + const key = useServerSWRKey(['activity', useFilteredObject(filters || {})]); + + return useSWR>( + key, + async () => { + const { data } = await http.get(`/api/client/servers/${uuid}/activity`, { + params: { + ...withQueryBuilderParams(filters), + include: ['actor'], + }, + }); + + return toPaginatedSet(data, Transformers.toActivityLog); + }, + { revalidateOnMount: false, ...(config || {}) } + ); +}; + +export { useActivityLogs }; diff --git a/resources/scripts/api/server/databases/createServerDatabase.ts b/resources/scripts/api/server/databases/createServerDatabase.ts index cf036f91fb..cb0c25b9e5 100644 --- a/resources/scripts/api/server/databases/createServerDatabase.ts +++ b/resources/scripts/api/server/databases/createServerDatabase.ts @@ -3,13 +3,17 @@ import http from '@/api/http'; export default (uuid: string, data: { connectionsFrom: string; databaseName: string }): Promise => { return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/databases`, { - database: data.databaseName, - remote: data.connectionsFrom, - }, { - params: { include: 'password' }, - }) - .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) + http.post( + `/api/client/servers/${uuid}/databases`, + { + database: data.databaseName, + remote: data.connectionsFrom, + }, + { + params: { include: 'password' }, + } + ) + .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/databases/getServerDatabases.ts b/resources/scripts/api/server/databases/getServerDatabases.ts index cf7c9037df..a03eb30a99 100644 --- a/resources/scripts/api/server/databases/getServerDatabases.ts +++ b/resources/scripts/api/server/databases/getServerDatabases.ts @@ -15,7 +15,7 @@ export const rawDataToServerDatabase = (data: any): ServerDatabase => ({ username: data.username, connectionString: `${data.host.address}:${data.host.port}`, allowConnectionsFrom: data.connections_from, - password: data.relationships && data.relationships.password ? data.relationships.password.attributes.password : undefined, + password: data.relationships.password?.attributes?.password, }); export default (uuid: string, includePassword = true): Promise => { @@ -23,9 +23,9 @@ export default (uuid: string, includePassword = true): Promise http.get(`/api/client/servers/${uuid}/databases`, { params: includePassword ? { include: 'password' } : undefined, }) - .then(response => resolve( - (response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes)) - )) + .then((response) => + resolve((response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes))) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/server/files/compressFiles.ts b/resources/scripts/api/server/files/compressFiles.ts index 4204f08847..b4c0a251ec 100644 --- a/resources/scripts/api/server/files/compressFiles.ts +++ b/resources/scripts/api/server/files/compressFiles.ts @@ -3,10 +3,15 @@ import http from '@/api/http'; import { rawDataToFileObject } from '@/api/transformers'; export default async (uuid: string, directory: string, files: string[]): Promise => { - const { data } = await http.post(`/api/client/servers/${uuid}/files/compress`, { root: directory, files }, { - timeout: 60000, - timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear once completed.', - }); + const { data } = await http.post( + `/api/client/servers/${uuid}/files/compress`, + { root: directory, files }, + { + timeout: 60000, + timeoutErrorMessage: + 'It looks like this archive is taking a long time to generate. It will appear once completed.', + } + ); return rawDataToFileObject(data); }; diff --git a/resources/scripts/api/server/files/decompressFiles.ts b/resources/scripts/api/server/files/decompressFiles.ts index d674eadb0a..37557a671c 100644 --- a/resources/scripts/api/server/files/decompressFiles.ts +++ b/resources/scripts/api/server/files/decompressFiles.ts @@ -1,8 +1,13 @@ import http from '@/api/http'; export default async (uuid: string, directory: string, file: string): Promise => { - await http.post(`/api/client/servers/${uuid}/files/decompress`, { root: directory, file }, { - timeout: 300000, - timeoutErrorMessage: 'It looks like this archive is taking a long time to be unarchived. Once completed the unarchived files will appear.', - }); + await http.post( + `/api/client/servers/${uuid}/files/decompress`, + { root: directory, file }, + { + timeout: 300000, + timeoutErrorMessage: + 'It looks like this archive is taking a long time to be unarchived. Once completed the unarchived files will appear.', + } + ); }; diff --git a/resources/scripts/api/server/files/getFileContents.ts b/resources/scripts/api/server/files/getFileContents.ts index ef25b1dbcc..66df376d39 100644 --- a/resources/scripts/api/server/files/getFileContents.ts +++ b/resources/scripts/api/server/files/getFileContents.ts @@ -4,7 +4,7 @@ export default (server: string, file: string): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${server}/files/contents`, { params: { file }, - transformResponse: res => res, + transformResponse: (res) => res, responseType: 'text', }) .then(({ data }) => resolve(data)) diff --git a/resources/scripts/api/server/files/loadDirectory.ts b/resources/scripts/api/server/files/loadDirectory.ts index ba137e2858..1f90b8ac33 100644 --- a/resources/scripts/api/server/files/loadDirectory.ts +++ b/resources/scripts/api/server/files/loadDirectory.ts @@ -5,7 +5,7 @@ export interface FileObject { key: string; name: string; mode: string; - modeBits: string, + modeBits: string; size: number; isFile: boolean; isSymlink: boolean; diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index 3d74ee2abe..16743ef870 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -1,6 +1,7 @@ import http, { FractalResponseData, FractalResponseList } from '@/api/http'; import { rawDataToServerAllocation, rawDataToServerEggVariable } from '@/api/transformers'; import { ServerEggVariable, ServerStatus } from '@/api/server/types'; +import { Identifier } from '@/api/definitions'; export interface Allocation { id: number; @@ -12,11 +13,28 @@ export interface Allocation { } export interface Server { - id: string; + /** + * This value is determined by the presence of the `PTERODACTYL_USE_SERVER_IDENTIFIERS` environment + * variable which changes what the API can respond with. It will eventually be removed and referenced + * as the "identifier" key, but this allows users to slowly opt-in to these new URLs and trial it + * as they wish. + * + * @deprecated this is the "uuid_short" which will be removed in 2.0, prefer use of "identifier" + */ + id: string | Identifier<'serv'>; + identifier: Identifier<'serv'>; // Set from "server_identifier" and should be used moving forward to reference a server. internalId: number | string; + /** + * Exists only to maintain support in cases where the short-uuid is necessary for server reference + * and cannot be easily replaced with "identifier". + * + * @deprecated + */ + __deprecatedUuidShort: string; uuid: string; name: string; node: string; + isNodeUnderMaintenance: boolean; status: ServerStatus; sftpDetails: { ip: string; @@ -39,7 +57,6 @@ export interface Server { allocations: number; backups: number; }; - isInstalling: boolean; isTransferring: boolean; variables: ServerEggVariable[]; allocations: Allocation[]; @@ -47,10 +64,13 @@ export interface Server { export const rawDataToServerObject = ({ attributes: data }: FractalResponseData): Server => ({ id: data.identifier, + identifier: data.server_identifier, internalId: data.internal_id, + __deprecatedUuidShort: data.__deprecated_uuid_short, uuid: data.uuid, name: data.name, node: data.node, + isNodeUnderMaintenance: data.is_node_under_maintenance, status: data.status, invocation: data.invocation, dockerImage: data.docker_image, @@ -58,24 +78,29 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) ip: data.sftp_details.ip, port: data.sftp_details.port, }, - description: data.description ? ((data.description.length > 0) ? data.description : null) : null, + description: data.description ? (data.description.length > 0 ? data.description : null) : null, limits: { ...data.limits }, eggFeatures: data.egg_features || [], featureLimits: { ...data.feature_limits }, - isInstalling: data.status === 'installing' || data.status === 'install_failed', isTransferring: data.is_transferring, - variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map(rawDataToServerEggVariable), - allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map(rawDataToServerAllocation), + variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map( + rawDataToServerEggVariable + ), + allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map( + rawDataToServerAllocation + ), }); -export default (uuid: string): Promise<[ Server, string[] ]> => { +export default (uuid: string): Promise<[Server, string[]]> => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${uuid}`) - .then(({ data }) => resolve([ - rawDataToServerObject(data), - // eslint-disable-next-line camelcase - data.meta?.is_server_owner ? [ '*' ] : (data.meta?.user_permissions || []), - ])) + .then(({ data }) => + resolve([ + rawDataToServerObject(data), + // eslint-disable-next-line camelcase + data.meta?.is_server_owner ? ['*'] : data.meta?.user_permissions || [], + ]) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/server/getServerResourceUsage.ts b/resources/scripts/api/server/getServerResourceUsage.ts index 2a4c01cf60..200ceb0179 100644 --- a/resources/scripts/api/server/getServerResourceUsage.ts +++ b/resources/scripts/api/server/getServerResourceUsage.ts @@ -16,16 +16,18 @@ export interface ServerStats { export default (server: string): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${server}/resources`) - .then(({ data: { attributes } }) => resolve({ - status: attributes.current_state, - isSuspended: attributes.is_suspended, - memoryUsageInBytes: attributes.resources.memory_bytes, - cpuUsagePercent: attributes.resources.cpu_absolute, - diskUsageInBytes: attributes.resources.disk_bytes, - networkRxInBytes: attributes.resources.network_rx_bytes, - networkTxInBytes: attributes.resources.network_tx_bytes, - uptime: attributes.resources.uptime, - })) + .then(({ data: { attributes } }) => + resolve({ + status: attributes.current_state, + isSuspended: attributes.is_suspended, + memoryUsageInBytes: attributes.resources.memory_bytes, + cpuUsagePercent: attributes.resources.cpu_absolute, + diskUsageInBytes: attributes.resources.disk_bytes, + networkRxInBytes: attributes.resources.network_rx_bytes, + networkTxInBytes: attributes.resources.network_tx_bytes, + uptime: attributes.resources.uptime, + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/server/getWebsocketToken.ts b/resources/scripts/api/server/getWebsocketToken.ts index da78dfd057..4769e188b9 100644 --- a/resources/scripts/api/server/getWebsocketToken.ts +++ b/resources/scripts/api/server/getWebsocketToken.ts @@ -8,10 +8,12 @@ interface Response { export default (server: string): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${server}/websocket`) - .then(({ data }) => resolve({ - token: data.data.token, - socket: data.data.socket, - })) + .then(({ data }) => + resolve({ + token: data.data.token, + socket: data.data.socket, + }) + ) .catch(reject); }); }; diff --git a/resources/scripts/api/server/network/deleteServerAllocation.ts b/resources/scripts/api/server/network/deleteServerAllocation.ts index 92fd4b30ac..6e45e1d3dd 100644 --- a/resources/scripts/api/server/network/deleteServerAllocation.ts +++ b/resources/scripts/api/server/network/deleteServerAllocation.ts @@ -1,4 +1,5 @@ import { Allocation } from '@/api/server/getServer'; import http from '@/api/http'; -export default async (uuid: string, id: number): Promise => await http.delete(`/api/client/servers/${uuid}/network/allocations/${id}`); +export default async (uuid: string, id: number): Promise => + await http.delete(`/api/client/servers/${uuid}/network/allocations/${id}`); diff --git a/resources/scripts/api/server/renameServer.ts b/resources/scripts/api/server/renameServer.ts index a4a95141e2..4622f9d4d7 100644 --- a/resources/scripts/api/server/renameServer.ts +++ b/resources/scripts/api/server/renameServer.ts @@ -1,8 +1,8 @@ import http from '@/api/http'; -export default (uuid: string, name: string): Promise => { +export default (uuid: string, name: string, description?: string): Promise => { return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/settings/rename`, { name }) + http.post(`/api/client/servers/${uuid}/settings/rename`, { name, description }) .then(() => resolve()) .catch(reject); }); diff --git a/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts index de1bc075b2..d864dee59e 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts @@ -1,7 +1,7 @@ import { rawDataToServerSchedule, Schedule } from '@/api/server/schedules/getServerSchedules'; import http from '@/api/http'; -type Data = Pick & { id?: number } +type Data = Pick & { id?: number }; export default async (uuid: string, schedule: Data): Promise => { const { data } = await http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, { diff --git a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts index c2bfc807bd..388d8d6d58 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts @@ -9,12 +9,15 @@ interface Data { } export default async (uuid: string, schedule: number, task: number | undefined, data: Data): Promise => { - const { data: response } = await http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, { - action: data.action, - payload: data.payload, - continue_on_failure: data.continueOnFailure, - time_offset: data.timeOffset, - }); + const { data: response } = await http.post( + `/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, + { + action: data.action, + payload: data.payload, + continue_on_failure: data.continueOnFailure, + time_offset: data.timeOffset, + } + ); return rawDataToServerTask(response.attributes); }; diff --git a/resources/scripts/api/server/schedules/getServerSchedule.ts b/resources/scripts/api/server/schedules/getServerSchedule.ts index 63e3d6f983..537124bd6d 100644 --- a/resources/scripts/api/server/schedules/getServerSchedule.ts +++ b/resources/scripts/api/server/schedules/getServerSchedule.ts @@ -5,7 +5,7 @@ export default (uuid: string, schedule: number): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${uuid}/schedules/${schedule}`, { params: { - include: [ 'tasks' ], + include: ['tasks'], }, }) .then(({ data }) => resolve(rawDataToServerSchedule(data.attributes))) diff --git a/resources/scripts/api/server/schedules/getServerSchedules.ts b/resources/scripts/api/server/schedules/getServerSchedules.ts index c052e7a3ce..aef756a0ed 100644 --- a/resources/scripts/api/server/schedules/getServerSchedules.ts +++ b/resources/scripts/api/server/schedules/getServerSchedules.ts @@ -69,7 +69,7 @@ export const rawDataToServerSchedule = (data: any): Schedule => ({ export default async (uuid: string): Promise => { const { data } = await http.get(`/api/client/servers/${uuid}/schedules`, { params: { - include: [ 'tasks' ], + include: ['tasks'], }, }); diff --git a/resources/scripts/api/server/types.d.ts b/resources/scripts/api/server/types.d.ts index 7e3e540c0a..c4a01e9211 100644 --- a/resources/scripts/api/server/types.d.ts +++ b/resources/scripts/api/server/types.d.ts @@ -1,4 +1,10 @@ -export type ServerStatus = 'installing' | 'install_failed' | 'suspended' | 'restoring_backup' | null; +export type ServerStatus = + | 'installing' + | 'install_failed' + | 'reinstall_failed' + | 'suspended' + | 'restoring_backup' + | null; export interface ServerBackup { uuid: string; @@ -17,7 +23,7 @@ export interface ServerEggVariable { description: string; envVariable: string; defaultValue: string; - serverValue: string; + serverValue: string | null; isEditable: boolean; rules: string[]; } diff --git a/resources/scripts/api/server/updateStartupVariable.ts b/resources/scripts/api/server/updateStartupVariable.ts index 74498caa8e..5102172822 100644 --- a/resources/scripts/api/server/updateStartupVariable.ts +++ b/resources/scripts/api/server/updateStartupVariable.ts @@ -2,8 +2,8 @@ import http from '@/api/http'; import { ServerEggVariable } from '@/api/server/types'; import { rawDataToServerEggVariable } from '@/api/transformers'; -export default async (uuid: string, key: string, value: string): Promise<[ ServerEggVariable, string ]> => { +export default async (uuid: string, key: string, value: string): Promise<[ServerEggVariable, string]> => { const { data } = await http.put(`/api/client/servers/${uuid}/startup/variable`, { key, value }); - return [ rawDataToServerEggVariable(data), data.meta.startup_command ]; + return [rawDataToServerEggVariable(data), data.meta.startup_command]; }; diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts index 93303a2db2..019d35c7aa 100644 --- a/resources/scripts/api/server/users/createOrUpdateSubuser.ts +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -12,7 +12,7 @@ export default (uuid: string, params: Params, subuser?: Subuser): Promise resolve(rawDataToServerSubuser(data.data))) + .then((data) => resolve(rawDataToServerSubuser(data.data))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts index 177bde815f..dae2ce5806 100644 --- a/resources/scripts/api/server/users/getServerSubusers.ts +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -9,7 +9,7 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ twoFactorEnabled: data.attributes['2fa_enabled'], createdAt: new Date(data.attributes.created_at), permissions: data.attributes.permissions || [], - can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, + can: (permission) => (data.attributes.permissions || []).indexOf(permission) >= 0, }); export default (uuid: string): Promise => { diff --git a/resources/scripts/api/swr/getServerAllocations.ts b/resources/scripts/api/swr/getServerAllocations.ts index a5591a3967..b254b6505c 100644 --- a/resources/scripts/api/swr/getServerAllocations.ts +++ b/resources/scripts/api/swr/getServerAllocations.ts @@ -5,11 +5,15 @@ import { rawDataToServerAllocation } from '@/api/transformers'; import { Allocation } from '@/api/server/getServer'; export default () => { - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - return useSWR([ 'server:allocations', uuid ], async () => { - const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); + return useSWR( + ['server:allocations', uuid], + async () => { + const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); - return (data.data || []).map(rawDataToServerAllocation); - }, { revalidateOnFocus: false, revalidateOnMount: false }); + return (data.data || []).map(rawDataToServerAllocation); + }, + { revalidateOnFocus: false, revalidateOnMount: false } + ); }; diff --git a/resources/scripts/api/swr/getServerBackups.ts b/resources/scripts/api/swr/getServerBackups.ts index 6b76ddcd11..dbc7f5d989 100644 --- a/resources/scripts/api/swr/getServerBackups.ts +++ b/resources/scripts/api/swr/getServerBackups.ts @@ -16,15 +16,15 @@ type BackupResponse = PaginatedResult & { backupCount: number }; export default () => { const { page } = useContext(Context); - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - return useSWR([ 'server:backups', uuid, page ], async () => { + return useSWR(['server:backups', uuid, page], async () => { const { data } = await http.get(`/api/client/servers/${uuid}/backups`, { params: { page } }); - return ({ + return { items: (data.data || []).map(rawDataToServerBackup), pagination: getPaginationSet(data.meta.pagination), backupCount: data.meta.backup_count, - }); + }; }); }; diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index b7089b7bd1..efecefe08d 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -1,4 +1,4 @@ -import useSWR from 'swr'; +import useSWR, { ConfigInterface } from 'swr'; import http, { FractalResponseList } from '@/api/http'; import { rawDataToServerEggVariable } from '@/api/transformers'; import { ServerEggVariable } from '@/api/server/types'; @@ -6,13 +6,22 @@ import { ServerEggVariable } from '@/api/server/types'; interface Response { invocation: string; variables: ServerEggVariable[]; - dockerImages: string[]; + dockerImages: Record; } -export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startup' ], async (): Promise => { - const { data } = await http.get(`/api/client/servers/${uuid}/startup`); +export default (uuid: string, initialData?: Response | null, config?: ConfigInterface) => + useSWR( + [uuid, '/startup'], + async (): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/startup`); - const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); + const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); - return { invocation: data.meta.startup_command, variables, dockerImages: data.meta.docker_images || [] }; -}, { initialData, errorRetryCount: 3 }); + return { + variables, + invocation: data.meta.startup_command, + dockerImages: data.meta.docker_images || {}, + }; + }, + { initialData: initialData || undefined, errorRetryCount: 3, ...(config || {}) } + ); diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index 069baf1264..602ac217d3 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -25,33 +25,32 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ modifiedAt: new Date(data.attributes.modified_at), isArchiveType: function () { - return this.isFile && [ - 'application/vnd.rar', // .rar - 'application/x-rar-compressed', // .rar (2) - 'application/x-tar', // .tar - 'application/x-br', // .tar.br - 'application/x-bzip2', // .tar.bz2, .bz2 - 'application/gzip', // .tar.gz, .gz - 'application/x-gzip', - 'application/x-lzip', // .tar.lz4, .lz4 (not sure if this mime type is correct) - 'application/x-sz', // .tar.sz, .sz (not sure if this mime type is correct) - 'application/x-xz', // .tar.xz, .xz - 'application/zstd', // .tar.zst, .zst - 'application/zip', // .zip - ].indexOf(this.mimetype) >= 0; + return ( + this.isFile && + [ + 'application/vnd.rar', // .rar + 'application/x-rar-compressed', // .rar (2) + 'application/x-tar', // .tar + 'application/x-br', // .tar.br + 'application/x-bzip2', // .tar.bz2, .bz2 + 'application/gzip', // .tar.gz, .gz + 'application/x-gzip', + 'application/x-lzip', // .tar.lz4, .lz4 (not sure if this mime type is correct) + 'application/x-sz', // .tar.sz, .sz (not sure if this mime type is correct) + 'application/x-xz', // .tar.xz, .xz + 'application/zstd', // .tar.zst, .zst + 'application/zip', // .zip + 'application/x-7z-compressed', // .7z + ].indexOf(this.mimetype) >= 0 + ); }, isEditable: function () { if (this.isArchiveType() || !this.isFile) return false; - const matches = [ - 'application/jar', - 'application/octet-stream', - 'inode/directory', - /^image\//, - ]; + const matches = ['application/jar', 'application/octet-stream', 'inode/directory', /^image\/(?!svg\+xml)/]; - return matches.every(m => !this.mimetype.match(m)); + return matches.every((m) => !this.mimetype.match(m)); }, }); diff --git a/resources/scripts/assets/css/GlobalStylesheet.ts b/resources/scripts/assets/css/GlobalStylesheet.ts index a38dff74e1..f7c2fe78d7 100644 --- a/resources/scripts/assets/css/GlobalStylesheet.ts +++ b/resources/scripts/assets/css/GlobalStylesheet.ts @@ -1,7 +1,18 @@ import tw from 'twin.macro'; import { createGlobalStyle } from 'styled-components/macro'; +// @ts-expect-error untyped font file +import font from '@fontsource-variable/ibm-plex-sans/files/ibm-plex-sans-latin-wght-normal.woff2'; export default createGlobalStyle` + @font-face { + font-family: 'IBM Plex Sans'; + font-style: normal; + font-display: swap; + font-weight: 100 700; + src: url(${font}) format('woff2-variations'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; + } + body { ${tw`font-sans bg-neutral-800 text-neutral-200`}; letter-spacing: 0.015em; @@ -60,12 +71,6 @@ export default createGlobalStyle` -webkit-border-radius: 4px 9px; } - ::-webkit-scrollbar-thumb:hover { - -webkit-box-shadow: - inset 0 0 0 1px hsl(212, 92%, 43%), - inset 0 0 0 4px hsl(212, 92%, 43%); - } - ::-webkit-scrollbar-corner { background: transparent; } diff --git a/resources/scripts/assets/tailwind.css b/resources/scripts/assets/tailwind.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/resources/scripts/assets/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index 1746fb6bfc..935400030b 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -1,18 +1,23 @@ -import React from 'react'; +import React, { lazy } from 'react'; import { hot } from 'react-hot-loader/root'; import { Route, Router, Switch } from 'react-router-dom'; import { StoreProvider } from 'easy-peasy'; import { store } from '@/state'; -import DashboardRouter from '@/routers/DashboardRouter'; -import ServerRouter from '@/routers/ServerRouter'; -import AuthenticationRouter from '@/routers/AuthenticationRouter'; import { SiteSettings } from '@/state/settings'; import ProgressBar from '@/components/elements/ProgressBar'; import { NotFound } from '@/components/elements/ScreenBlock'; -import tw, { GlobalStyles as TailwindGlobalStyles } from 'twin.macro'; +import tw from 'twin.macro'; import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; import { history } from '@/components/history'; import { setupInterceptors } from '@/api/interceptors'; +import AuthenticatedRoute from '@/components/elements/AuthenticatedRoute'; +import { ServerContext } from '@/state/server'; +import '@/assets/tailwind.css'; +import Spinner from '@/components/elements/Spinner'; + +const DashboardRouter = lazy(() => import(/* webpackChunkName: "dashboard" */ '@/routers/DashboardRouter')); +const ServerRouter = lazy(() => import(/* webpackChunkName: "server" */ '@/routers/ServerRouter')); +const AuthenticationRouter = lazy(() => import(/* webpackChunkName: "auth" */ '@/routers/AuthenticationRouter')); interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -33,7 +38,7 @@ interface ExtendedWindow extends Window { setupInterceptors(history); const App = () => { - const { PterodactylUser, SiteConfiguration } = (window as ExtendedWindow); + const { PterodactylUser, SiteConfiguration } = window as ExtendedWindow; if (PterodactylUser && !store.getState().user.data) { store.getActions().user.setUserData({ uuid: PterodactylUser.uuid, @@ -53,17 +58,32 @@ const App = () => { return ( <> - - + - +

- - - - + + + + + + + + + + + + + + + + + + + +
diff --git a/resources/scripts/components/Avatar.tsx b/resources/scripts/components/Avatar.tsx new file mode 100644 index 0000000000..4d9d254dbe --- /dev/null +++ b/resources/scripts/components/Avatar.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import BoringAvatar, { AvatarProps } from 'boring-avatars'; +import { useStoreState } from '@/state/hooks'; + +const palette = ['#FFAD08', '#EDD75A', '#73B06F', '#0C8F8F', '#587291']; + +type Props = Omit; + +const _Avatar = ({ variant = 'beam', ...props }: AvatarProps) => ( + +); + +const _UserAvatar = ({ variant = 'beam', ...props }: Omit) => { + const uuid = useStoreState((state) => state.user.data?.uuid); + + return ; +}; + +_Avatar.displayName = 'Avatar'; +_UserAvatar.displayName = 'Avatar.User'; + +const Avatar = Object.assign(_Avatar, { + User: _UserAvatar, +}); + +export default Avatar; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 6bd22f891f..8d0b43f2b7 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -9,27 +9,22 @@ type Props = Readonly<{ }>; const FlashMessageRender = ({ byKey, className }: Props) => { - const flashes = useStoreState(state => state.flashes.items.filter( - flash => byKey ? flash.key === byKey : true, - )); - - return ( - flashes.length ? -
- { - flashes.map((flash, index) => ( - - {index > 0 &&
} - - {flash.message} - -
- )) - } -
- : - null + const flashes = useStoreState((state) => + state.flashes.items.filter((flash) => (byKey ? flash.key === byKey : true)) ); + + return flashes.length ? ( +
+ {flashes.map((flash, index) => ( + + {index > 0 &&
} + + {flash.message} + +
+ ))} +
+ ) : null; }; export default FlashMessageRender; diff --git a/resources/scripts/components/MessageBox.tsx b/resources/scripts/components/MessageBox.tsx index e16986ed50..57a5cacb5b 100644 --- a/resources/scripts/components/MessageBox.tsx +++ b/resources/scripts/components/MessageBox.tsx @@ -42,26 +42,24 @@ const getBackground = (type?: FlashMessageType): TwStyle | string => { const Container = styled.div<{ $type?: FlashMessageType }>` ${tw`p-2 border items-center leading-normal rounded flex w-full text-sm text-white`}; - ${props => styling(props.$type)}; + ${(props) => styling(props.$type)}; `; Container.displayName = 'MessageBox.Container'; const MessageBox = ({ title, children, type }: Props) => ( - {title && - - {title} - - } - - {children} - + {title && ( + + {title} + + )} + {children} ); MessageBox.displayName = 'MessageBox'; diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx index 9500d215d3..6142f62687 100644 --- a/resources/scripts/components/NavigationBar.tsx +++ b/resources/scripts/components/NavigationBar.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; +import { useState } from 'react'; import { Link, NavLink } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCogs, faLayerGroup, faSignOutAlt, faUserCircle } from '@fortawesome/free-solid-svg-icons'; +import { faCogs, faLayerGroup, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import SearchContainer from '@/components/dashboard/search/SearchContainer'; @@ -9,36 +10,24 @@ import tw, { theme } from 'twin.macro'; import styled from 'styled-components/macro'; import http from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { useState } from 'react'; - -const Navigation = styled.div` - ${tw`w-full bg-neutral-900 shadow-md overflow-x-auto`}; - - & > div { - ${tw`mx-auto w-full flex items-center`}; - } - - & #logo { - ${tw`flex-1`}; - - & > a { - ${tw`text-2xl font-header px-4 no-underline text-neutral-200 hover:text-neutral-100 transition-colors duration-150`}; - } - } -`; +import Tooltip from '@/components/elements/tooltip/Tooltip'; +import Avatar from '@/components/Avatar'; const RightNavigation = styled.div` - ${tw`flex h-full items-center justify-center`}; - - & > a, & > button, & > .navigation-link { + & > a, + & > button, + & > .navigation-link { ${tw`flex items-center h-full no-underline text-neutral-300 px-6 cursor-pointer transition-all duration-150`}; - - &:active, &:hover { + + &:active, + &:hover { ${tw`text-neutral-100 bg-black`}; } - - &:active, &:hover, &.active { - box-shadow: inset 0 -2px ${theme`colors.cyan.700`.toString()}; + + &:active, + &:hover, + &.active { + box-shadow: inset 0 -2px ${theme`colors.cyan.600`.toString()}; } } `; @@ -46,43 +35,58 @@ const RightNavigation = styled.div` export default () => { const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); const rootAdmin = useStoreState((state: ApplicationStore) => state.user.data!.rootAdmin); - const [ isLoggingOut, setIsLoggingOut ] = useState(false); + const [isLoggingOut, setIsLoggingOut] = useState(false); const onTriggerLogout = () => { setIsLoggingOut(true); http.post('/auth/logout').finally(() => { - // @ts-ignore + // @ts-expect-error this is valid window.location = '/'; }); }; return ( - +
-
-
- +
+
+ {name}
- - - - - - - - - {rootAdmin && - - - - } - + + + + + + + + {rootAdmin && ( + + + + + + )} + + + + + + + + + +
- +
); }; diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 57f91f4c1a..76ddf19924 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -19,10 +19,10 @@ interface Values { export default () => { const ref = useRef(null); - const [ token, setToken ] = useState(''); + const [token, setToken] = useState(''); const { clearFlashes, addFlash } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); useEffect(() => { clearFlashes(); @@ -34,7 +34,7 @@ export default () => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch(error => { + ref.current!.execute().catch((error) => { console.error(error); setSubmitting(false); @@ -45,11 +45,11 @@ export default () => { } requestPasswordResetEmail(email, token) - .then(response => { + .then((response) => { resetForm(); addFlash({ type: 'success', title: 'Success', message: response }); }) - .catch(error => { + .catch((error) => { console.error(error); addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); }) @@ -66,47 +66,42 @@ export default () => { onSubmit={handleSubmission} initialValues={{ email: '' }} validationSchema={object().shape({ - email: string().email('A valid email address must be provided to continue.') + email: string() + .email('A valid email address must be provided to continue.') .required('A valid email address must be provided to continue.'), })} > {({ isSubmitting, setSubmitting, submitForm }) => ( - +
-
- {recaptchaEnabled && - { - setToken(response); - submitForm(); - }} - onExpire={() => { - setSubmitting(false); - setToken(''); - }} - /> - } + {recaptchaEnabled && ( + { + setToken(response); + submitForm(); + }} + onExpire={() => { + setSubmitting(false); + setToken(''); + }} + /> + )}
, StaticContext, { token?: string }> +type OwnProps = RouteComponentProps, StaticContext, { token?: string }>; type Props = OwnProps & { clearAndAddHttpError: ActionCreator; -} +}; const LoginCheckpointContainer = () => { const { isSubmitting, setFieldValue } = useFormikContext(); - const [ isMissingDevice, setIsMissingDevice ] = useState(false); + const [isMissingDevice, setIsMissingDevice] = useState(false); return ( @@ -39,16 +39,12 @@ const LoginCheckpointContainer = () => { : 'Enter the two-factor token generated by your device.' } type={'text'} + autoComplete={'one-time-code'} autoFocus />
-
@@ -57,11 +53,11 @@ const LoginCheckpointContainer = () => { onClick={() => { setFieldValue('code', ''); setFieldValue('recoveryCode', ''); - setIsMissingDevice(s => !s); + setIsMissingDevice((s) => !s); }} css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`} > - {!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'} + {!isMissingDevice ? "I've Lost My Device" : 'I Have My Device'}
@@ -79,16 +75,16 @@ const LoginCheckpointContainer = () => { const EnhancedForm = withFormik({ handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { clearAndAddHttpError, location } }) => { loginCheckpoint(location.state?.token || '', code, recoveryCode) - .then(response => { + .then((response) => { if (response.complete) { - // @ts-ignore + // @ts-expect-error this is valid window.location = response.intended || '/'; return; } setSubmitting(false); }) - .catch(error => { + .catch((error) => { console.error(error); setSubmitting(false); clearAndAddHttpError({ error }); @@ -110,10 +106,7 @@ export default ({ history, location, ...props }: OwnProps) => { return null; } - return ; + return ( + + ); }; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 8157111ee1..453a27ecc3 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -18,10 +18,10 @@ interface Values { const LoginContainer = ({ history }: RouteComponentProps) => { const ref = useRef(null); - const [ token, setToken ] = useState(''); + const [token, setToken] = useState(''); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); useEffect(() => { clearFlashes(); @@ -33,7 +33,7 @@ const LoginContainer = ({ history }: RouteComponentProps) => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch(error => { + ref.current!.execute().catch((error) => { console.error(error); setSubmitting(false); @@ -44,16 +44,16 @@ const LoginContainer = ({ history }: RouteComponentProps) => { } login({ ...values, recaptchaData: token }) - .then(response => { + .then((response) => { if (response.complete) { - // @ts-ignore + // @ts-expect-error this is valid window.location = response.intended || '/'; return; } history.replace('/auth/login/checkpoint', { token: response.confirmationToken }); }) - .catch(error => { + .catch((error) => { console.error(error); setToken(''); @@ -75,42 +75,30 @@ const LoginContainer = ({ history }: RouteComponentProps) => { > {({ isSubmitting, setSubmitting, submitForm }) => ( - +
- +
- {recaptchaEnabled && - { - setToken(response); - submitForm(); - }} - onExpire={() => { - setSubmitting(false); - setToken(''); - }} - /> - } + {recaptchaEnabled && ( + { + setToken(response); + submitForm(); + }} + onExpire={() => { + setSubmitting(false); + setToken(''); + }} + /> + )}
, HTMLFormElement> & { title?: string; -} +}; const Container = styled.div` ${breakpoint('sm')` @@ -30,24 +30,18 @@ const Container = styled.div` export default forwardRef(({ title, ...props }, ref) => ( - {title && -

- {title} -

- } - + {title &&

{title}

} +
- -
-
- {props.children} +
+
{props.children}

- © 2015 - {(new Date()).getFullYear()}  + © 2015 - {new Date().getFullYear()}  ) => { - const [ email, setEmail ] = useState(''); + const [email, setEmail] = useState(''); const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); - const parsed = parse(location.search); - if (email.length === 0 && parsed.email) { - setEmail(parsed.email as string); + const parsed = new URLSearchParams(location.search); + if (email.length === 0 && parsed.get('email')) { + setEmail(parsed.get('email') || ''); } const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) .then(() => { - // @ts-ignore + // @ts-expect-error this is valid window.location = '/'; }) - .catch(error => { + .catch((error) => { console.error(error); setSubmitting(false); @@ -52,22 +51,20 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => passwordConfirmation: '', }} validationSchema={object().shape({ - password: string().required('A new password is required.') + password: string() + .required('A new password is required.') .min(8, 'Your new password should be at least 8 characters in length.'), passwordConfirmation: string() .required('Your new password does not match.') - // @ts-ignore - .oneOf([ ref('password'), null ], 'Your new password does not match.'), + // @ts-expect-error this is valid + .oneOf([ref('password'), null], 'Your new password does not match.'), })} > {({ isSubmitting }) => ( - +

) => />
- +
-
diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index f3497d9943..282f190929 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -5,106 +5,90 @@ import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; -import ConfirmationModal from '@/components/elements/ConfirmationModal'; import deleteApiKey from '@/api/account/deleteApiKey'; -import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; import FlashMessageRender from '@/components/FlashMessageRender'; -import { httpErrorToHuman } from '@/api/http'; import { format } from 'date-fns'; import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; +import { Dialog } from '@/components/elements/dialog'; +import { useFlashKey } from '@/plugins/useFlash'; +import Code from '@/components/elements/Code'; export default () => { - const [ deleteIdentifier, setDeleteIdentifier ] = useState(''); - const [ keys, setKeys ] = useState([]); - const [ loading, setLoading ] = useState(true); - const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const [deleteIdentifier, setDeleteIdentifier] = useState(''); + const [keys, setKeys] = useState([]); + const [loading, setLoading] = useState(true); + const { clearAndAddHttpError } = useFlashKey('account'); useEffect(() => { - clearFlashes('account'); getApiKeys() - .then(keys => setKeys(keys)) + .then((keys) => setKeys(keys)) .then(() => setLoading(false)) - .catch(error => { - console.error(error); - addError({ key: 'account', message: httpErrorToHuman(error) }); - }); + .catch((error) => clearAndAddHttpError(error)); }, []); const doDeletion = (identifier: string) => { setLoading(true); - clearFlashes('account'); + + clearAndAddHttpError(); deleteApiKey(identifier) - .then(() => setKeys(s => ([ - ...(s || []).filter(key => key.identifier !== identifier), - ]))) - .catch(error => { - console.error(error); - addError({ key: 'account', message: httpErrorToHuman(error) }); - }) - .then(() => setLoading(false)); + .then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)])) + .catch((error) => clearAndAddHttpError(error)) + .then(() => { + setLoading(false); + setDeleteIdentifier(''); + }); }; return ( - +
- setKeys(s => ([ ...s!, key ]))}/> + setKeys((s) => [...s!, key])} /> - - { - doDeletion(deleteIdentifier); - setDeleteIdentifier(''); - }} - onModalDismissed={() => setDeleteIdentifier('')} + + setDeleteIdentifier('')} + onConfirmed={() => doDeletion(deleteIdentifier)} > - Are you sure you wish to delete this API key? All requests using it will immediately be - invalidated and will fail. - - { - keys.length === 0 ? -

- {loading ? 'Loading...' : 'No API keys exist for this account.'} -

- : - keys.map((key, index) => ( - 0 && tw`mt-2` ]} - > - -
-

{key.description}

-

- Last used:  - {key.lastUsedAt ? format(key.lastUsedAt, 'MMM do, yyyy HH:mm') : 'Never'} -

-
-

+ {loading ? 'Loading...' : 'No API keys exist for this account.'} +

+ ) : ( + keys.map((key, index) => ( + 0 && tw`mt-2`]} + > + +
+

{key.description}

+

+ Last used:  + {key.lastUsedAt ? format(key.lastUsedAt, 'MMM do, yyyy HH:mm') : 'Never'}

- - - )) - } +
+ + +
+ )) + )}
diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index 646f7690eb..4f64aec48c 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -11,19 +11,19 @@ import MessageBox from '@/components/MessageBox'; import { useLocation } from 'react-router-dom'; const Container = styled.div` - ${tw`flex flex-wrap`}; + ${tw`flex flex-wrap`}; - & > div { - ${tw`w-full`}; + & > div { + ${tw`w-full`}; - ${breakpoint('sm')` + ${breakpoint('sm')` width: calc(50% - 1rem); `} - ${breakpoint('md')` + ${breakpoint('md')` ${tw`w-auto flex-1`}; `} - } + } `; export default () => { @@ -31,28 +31,23 @@ export default () => { return ( - {state?.twoFactorRedirect && - - Your account must have two-factor authentication enabled in order to continue. - - } + {state?.twoFactorRedirect && ( + + Your account must have two-factor authentication enabled in order to continue. + + )} - + - + - - + + - - + + - ); }; diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx index e2c129a513..6d01029e27 100644 --- a/resources/scripts/components/dashboard/ApiKeyModal.tsx +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -20,7 +20,9 @@ const ApiKeyModal = ({ apiKey }: Props) => { shown again.

-                {apiKey}
+                
+                    {apiKey}
+                
             
+ {isEnabled ? ( + setVisible('disable')}>Disable Two-Step + ) : ( + + )}
); diff --git a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx index 9022ae6c84..9c273044cb 100644 --- a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx +++ b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx @@ -19,10 +19,12 @@ interface Values { allowedIps: string; } -const CustomTextarea = styled(Textarea)`${tw`h-32`}`; +const CustomTextarea = styled(Textarea)` + ${tw`h-32`} +`; export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { - const [ apiKey, setApiKey ] = useState(''); + const [apiKey, setApiKey] = useState(''); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const submit = (values: Values, { setSubmitting, resetForm }: FormikHelpers) => { @@ -34,7 +36,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { setApiKey(`${key.identifier}${secretToken}`); onKeyCreated(key); }) - .catch(error => { + .catch((error) => { console.error(error); addError({ key: 'account', message: httpErrorToHuman(error) }); @@ -44,11 +46,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { return ( <> - 0} - onModalDismissed={() => setApiKey('')} - apiKey={apiKey} - /> + 0} onModalDismissed={() => setApiKey('')} apiKey={apiKey} /> void }) => { > {({ isSubmitting }) => (
- + - + - +
diff --git a/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx new file mode 100644 index 0000000000..61354df5fc --- /dev/null +++ b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx @@ -0,0 +1,72 @@ +import React, { useContext, useEffect, useState } from 'react'; +import asDialog from '@/hoc/asDialog'; +import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; +import { Button } from '@/components/elements/button/index'; +import { Input } from '@/components/elements/inputs'; +import Tooltip from '@/components/elements/tooltip/Tooltip'; +import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor'; +import { useFlashKey } from '@/plugins/useFlash'; +import { useStoreActions } from '@/state/hooks'; +import FlashMessageRender from '@/components/FlashMessageRender'; + +const DisableTOTPDialog = () => { + const [submitting, setSubmitting] = useState(false); + const [password, setPassword] = useState(''); + const { clearAndAddHttpError } = useFlashKey('account:two-step'); + const { close, setProps } = useContext(DialogWrapperContext); + const updateUserData = useStoreActions((actions) => actions.user.updateUserData); + + useEffect(() => { + setProps((state) => ({ ...state, preventExternalClose: submitting })); + }, [submitting]); + + const submit = (e: React.FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (submitting) return; + + setSubmitting(true); + clearAndAddHttpError(); + disableAccountTwoFactor(password) + .then(() => { + updateUserData({ useTotp: false }); + close(); + }) + .catch(clearAndAddHttpError) + .then(() => setSubmitting(false)); + }; + + return ( + + + + ) => setPassword(e.currentTarget.value)} + /> + + Cancel + 0} + content={'You must enter your account password to continue.'} + > + + Disable + + + + + ); +}; + +export default asDialog({ + title: 'Disable Two-Step Verification', + description: 'Disabling two-step verification will make your account less secure.', +})(DisableTOTPDialog); diff --git a/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx deleted file mode 100644 index 854d0837a4..0000000000 --- a/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useContext } from 'react'; -import { Form, Formik, FormikHelpers } from 'formik'; -import FlashMessageRender from '@/components/FlashMessageRender'; -import Field from '@/components/elements/Field'; -import { object, string } from 'yup'; -import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; -import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor'; -import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; -import asModal from '@/hoc/asModal'; -import ModalContext from '@/context/ModalContext'; - -interface Values { - password: string; -} - -const DisableTwoFactorModal = () => { - const { dismiss, setPropOverrides } = useContext(ModalContext); - const { clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); - const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); - - const submit = ({ password }: Values, { setSubmitting }: FormikHelpers) => { - setPropOverrides({ showSpinnerOverlay: true, dismissable: false }); - disableAccountTwoFactor(password) - .then(() => { - updateUserData({ useTotp: false }); - dismiss(); - }) - .catch(error => { - console.error(error); - - clearAndAddHttpError({ error, key: 'account:two-factor' }); - setSubmitting(false); - setPropOverrides(null); - }); - }; - - return ( - - {({ isValid }) => ( -
- - -
- -
- - )} -
- ); -}; - -export default asModal()(DisableTwoFactorModal); diff --git a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx new file mode 100644 index 0000000000..3e2d0ba20b --- /dev/null +++ b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Dialog, DialogProps } from '@/components/elements/dialog'; +import { Button } from '@/components/elements/button/index'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import { Alert } from '@/components/elements/alert'; + +interface RecoveryTokenDialogProps extends DialogProps { + tokens: string[]; +} + +export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => { + const grouped = [] as [string, string][]; + tokens.forEach((token, index) => { + if (index % 2 === 0) { + grouped.push([token, tokens[index + 1] || '']); + } + }); + + return ( + + + +
+                    {grouped.map((value) => (
+                        
+                            {value[0]}
+                             
+                            {value[1]}
+                             
+                        
+                    ))}
+                
+
+ + These codes will not be shown again. + + + Done + +
+ ); +}; diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx new file mode 100644 index 0000000000..6eb0cac277 --- /dev/null +++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx @@ -0,0 +1,130 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; +import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData'; +import { useFlashKey } from '@/plugins/useFlash'; +import tw from 'twin.macro'; +import QRCode from 'qrcode.react'; +import { Button } from '@/components/elements/button/index'; +import Spinner from '@/components/elements/Spinner'; +import { Input } from '@/components/elements/inputs'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import Tooltip from '@/components/elements/tooltip/Tooltip'; +import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import asDialog from '@/hoc/asDialog'; + +interface Props { + onTokens: (tokens: string[]) => void; +} + +const ConfigureTwoFactorForm = ({ onTokens }: Props) => { + const [submitting, setSubmitting] = useState(false); + const [value, setValue] = useState(''); + const [password, setPassword] = useState(''); + const [token, setToken] = useState(null); + const { clearAndAddHttpError } = useFlashKey('account:two-step'); + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); + + const { close, setProps } = useContext(DialogWrapperContext); + + useEffect(() => { + getTwoFactorTokenData() + .then(setToken) + .catch((error) => clearAndAddHttpError(error)); + }, []); + + useEffect(() => { + setProps((state) => ({ ...state, preventExternalClose: submitting })); + }, [submitting]); + + const submit = (e: React.FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (submitting) return; + + setSubmitting(true); + clearAndAddHttpError(); + enableAccountTwoFactor(value, password) + .then((tokens) => { + updateUserData({ useTotp: true }); + onTokens(tokens); + }) + .catch((error) => { + clearAndAddHttpError(error); + setSubmitting(false); + }); + }; + + return ( +
+ +
+ {!token ? ( + + ) : ( + + )} +
+ +

+ {token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'} +

+
+

+ Scan the QR code above using the two-step authentication app of your choice. Then, enter the 6-digit + code generated into the field below. +

+ ) => setValue(e.currentTarget.value)} + className={'mt-3'} + placeholder={'000000'} + type={'text'} + inputMode={'numeric'} + autoComplete={'one-time-code'} + pattern={'\\d{6}'} + /> + + ) => setPassword(e.currentTarget.value)} + /> + + Cancel + 0 && value.length === 6} + content={ + !token + ? 'Waiting for QR code to load...' + : 'You must enter the 6-digit code and your password to continue.' + } + delay={100} + > + + + + + ); +}; + +export default asDialog({ + title: 'Enable Two-Step Verification', + description: + "Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in.", +})(ConfigureTwoFactorForm); diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx deleted file mode 100644 index aea6039958..0000000000 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Form, Formik, FormikHelpers } from 'formik'; -import { object, string } from 'yup'; -import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData'; -import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; -import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; -import FlashMessageRender from '@/components/FlashMessageRender'; -import Field from '@/components/elements/Field'; -import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; -import asModal from '@/hoc/asModal'; -import ModalContext from '@/context/ModalContext'; -import QRCode from 'qrcode.react'; -import CopyOnClick from '@/components/elements/CopyOnClick'; - -interface Values { - code: string; -} - -const SetupTwoFactorModal = () => { - const [ token, setToken ] = useState(null); - const [ recoveryTokens, setRecoveryTokens ] = useState([]); - - const { dismiss, setPropOverrides } = useContext(ModalContext); - const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); - const { clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); - - useEffect(() => { - getTwoFactorTokenData() - .then(setToken) - .catch(error => { - console.error(error); - clearAndAddHttpError({ error, key: 'account:two-factor' }); - }); - }, []); - - const submit = ({ code }: Values, { setSubmitting }: FormikHelpers) => { - setPropOverrides(state => ({ ...state, showSpinnerOverlay: true })); - enableAccountTwoFactor(code) - .then(tokens => { - setRecoveryTokens(tokens); - }) - .catch(error => { - console.error(error); - - clearAndAddHttpError({ error, key: 'account:two-factor' }); - }) - .then(() => { - setSubmitting(false); - setPropOverrides(state => ({ ...state, showSpinnerOverlay: false })); - }); - }; - - useEffect(() => { - setPropOverrides(state => ({ - ...state, - closeOnEscape: !recoveryTokens.length, - closeOnBackground: !recoveryTokens.length, - })); - - return () => { - if (recoveryTokens.length > 0) { - updateUserData({ useTotp: true }); - } - }; - }, [ recoveryTokens ]); - - return ( - - {recoveryTokens.length > 0 ? - <> -

Two-factor authentication enabled

-

- Two-factor authentication has been enabled on your account. Should you lose access to - your authenticator device, you'll need to use one of the codes displayed below in order to access your - account. -

-

- These codes will not be displayed again. Please take note of them now - by storing them in a secure repository such as a password manager. -

-
-                        {recoveryTokens.map(token => {token})}
-                    
-
- -
- - : -
- -
-
-
- {!token ? - - : - - } -
-
-
-
- - {token && -
- Alternatively, enter the following token into your authenticator application: - -
- - {token.secret} - -
-
-
- } -
-
- -
-
-
- - } -
- ); -}; - -export default asModal()(SetupTwoFactorModal); diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx index 547cb26ce5..462a37f1a1 100644 --- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -7,7 +7,7 @@ import Field from '@/components/elements/Field'; import { httpErrorToHuman } from '@/api/http'; import { ApplicationStore } from '@/state'; import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; +import { Button } from '@/components/elements/button/index'; interface Values { email: string; @@ -29,17 +29,21 @@ export default () => { clearFlashes('account:email'); updateEmail({ ...values }) - .then(() => addFlash({ - type: 'success', - key: 'account:email', - message: 'Your primary email has been updated.', - })) - .catch(error => addFlash({ - type: 'error', - key: 'account:email', - title: 'Error', - message: httpErrorToHuman(error), - })) + .then(() => + addFlash({ + type: 'success', + key: 'account:email', + message: 'Your primary email has been updated.', + }) + ) + .catch((error) => + addFlash({ + type: 'error', + key: 'account:email', + title: 'Error', + message: httpErrorToHuman(error), + }) + ) .then(() => { resetForm(); setSubmitting(false); @@ -47,39 +51,26 @@ export default () => { }; return ( - - { - ({ isSubmitting, isValid }) => ( - - -
+ + {({ isSubmitting, isValid }) => ( + + + + +
-
- -
-
- -
- - - ) - } +
+
+ +
+ +
+ )}
); }; diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index 1c91ef4d99..707ee72d7e 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -8,7 +8,7 @@ import updateAccountPassword from '@/api/account/updateAccountPassword'; import { httpErrorToHuman } from '@/api/http'; import { ApplicationStore } from '@/state'; import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; +import { Button } from '@/components/elements/button/index'; interface Values { current: string; @@ -19,9 +19,13 @@ interface Values { const schema = Yup.object().shape({ current: Yup.string().min(1).required('You must provide your current password.'), password: Yup.string().min(8).required(), - confirmPassword: Yup.string().test('password', 'Password confirmation does not match the password you entered.', function (value) { - return value === this.parent.password; - }), + confirmPassword: Yup.string().test( + 'password', + 'Password confirmation does not match the password you entered.', + function (value) { + return value === this.parent.password; + } + ), }); export default () => { @@ -36,15 +40,17 @@ export default () => { clearFlashes('account:password'); updateAccountPassword({ ...values }) .then(() => { - // @ts-ignore + // @ts-expect-error this is valid window.location = '/auth/login'; }) - .catch(error => addFlash({ - key: 'account:password', - type: 'error', - title: 'Error', - message: httpErrorToHuman(error), - })) + .catch((error) => + addFlash({ + key: 'account:password', + type: 'error', + title: 'Error', + message: httpErrorToHuman(error), + }) + ) .then(() => setSubmitting(false)); }; @@ -55,43 +61,41 @@ export default () => { validationSchema={schema} initialValues={{ current: '', password: '', confirmPassword: '' }} > - { - ({ isSubmitting, isValid }) => ( - - -
+ {({ isSubmitting, isValid }) => ( + + + + +
-
- -
-
- -
-
- -
- - - ) - } +
+
+ +
+
+ +
+ +
+ )}
); diff --git a/resources/scripts/components/dashboard/search/SearchContainer.tsx b/resources/scripts/components/dashboard/search/SearchContainer.tsx index 36c3ae871a..4da0f3a3f2 100644 --- a/resources/scripts/components/dashboard/search/SearchContainer.tsx +++ b/resources/scripts/components/dashboard/search/SearchContainer.tsx @@ -3,12 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch } from '@fortawesome/free-solid-svg-icons'; import useEventListener from '@/plugins/useEventListener'; import SearchModal from '@/components/dashboard/search/SearchModal'; +import Tooltip from '@/components/elements/tooltip/Tooltip'; export default () => { - const [ visible, setVisible ] = useState(false); + const [visible, setVisible] = useState(false); useEventListener('keydown', (e: KeyboardEvent) => { - if ([ 'input', 'textarea' ].indexOf(((e.target as HTMLElement).tagName || 'input').toLowerCase()) < 0) { + if (['input', 'textarea'].indexOf(((e.target as HTMLElement).tagName || 'input').toLowerCase()) < 0) { if (!visible && e.metaKey && e.key.toLowerCase() === '/') { setVisible(true); } @@ -17,16 +18,12 @@ export default () => { return ( <> - {visible && - setVisible(false)} - /> - } -
setVisible(true)}> - -
+ {visible && setVisible(false)} />} + +
setVisible(true)}> + +
+
); }; diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx index c35e0efcb5..1335cff438 100644 --- a/resources/scripts/components/dashboard/search/SearchModal.tsx +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -13,7 +13,8 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components/macro'; import tw from 'twin.macro'; import Input from '@/components/elements/Input'; -import { formatIp } from '@/helpers'; +import { ip } from '@/lib/formatters'; + type Props = RequiredModalProps; interface Values { @@ -39,24 +40,26 @@ const SearchWatcher = () => { if (values.term.length >= 3) { submitForm(); } - }, [ values.term ]); + }, [values.term]); return null; }; export default ({ ...props }: Props) => { const ref = useRef(null); - const isAdmin = useStoreState(state => state.user.data!.rootAdmin); - const [ servers, setServers ] = useState([]); - const { clearAndAddHttpError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const [servers, setServers] = useState([]); + const { clearAndAddHttpError, clearFlashes } = useStoreActions( + (actions: Actions) => actions.flashes + ); const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('search'); // if (ref.current) ref.current.focus(); getServers({ query: term, type: isAdmin ? 'admin-all' : undefined }) - .then(servers => setServers(servers.items.filter((_, index) => index < 5))) - .catch(error => { + .then((servers) => setServers(servers.items.filter((_, index) => index < 5))) + .catch((error) => { console.error(error); clearAndAddHttpError({ key: 'search', error }); }) @@ -68,10 +71,10 @@ export default ({ ...props }: Props) => { if (props.visible) { if (ref.current) ref.current.focus(); } - }, [ props.visible ]); + }, [props.visible]); // Formik does not support an innerRef on custom components. - const InputWithRef = (props: any) => ; + const InputWithRef = (props: any) => ; return ( { label={'Search term'} description={'Enter a server name, uuid, or allocation to begin searching.'} > - + - + - {servers.length > 0 && -
- { - servers.map(server => ( + {servers.length > 0 && ( +
+ {servers.map((server) => ( {

{server.name}

- { - server.allocations.filter(alloc => alloc.isDefault).map(allocation => ( - {allocation.alias || formatIp(allocation.ip)}:{allocation.port} - )) - } + {server.allocations + .filter((alloc) => alloc.isDefault) + .map((allocation) => ( + + {allocation.alias || ip(allocation.ip)}:{allocation.port} + + ))}

@@ -120,10 +124,9 @@ export default ({ ...props }: Props) => {
- )) - } -
- } + ))} +
+ )} )}
diff --git a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx new file mode 100644 index 0000000000..0308b39115 --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx @@ -0,0 +1,63 @@ +import React, { useEffect } from 'react'; +import ContentBox from '@/components/elements/ContentBox'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; +import { useSSHKeys } from '@/api/account/ssh-keys'; +import { useFlashKey } from '@/plugins/useFlash'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faKey } from '@fortawesome/free-solid-svg-icons'; +import { format } from 'date-fns'; +import CreateSSHKeyForm from '@/components/dashboard/ssh/CreateSSHKeyForm'; +import DeleteSSHKeyButton from '@/components/dashboard/ssh/DeleteSSHKeyButton'; + +export default () => { + const { clearAndAddHttpError } = useFlashKey('account'); + const { data, isValidating, error } = useSSHKeys({ + revalidateOnMount: true, + revalidateOnFocus: false, + }); + + useEffect(() => { + clearAndAddHttpError(error); + }, [error]); + + return ( + + +
+ + + + + + {!data || !data.length ? ( +

+ {!data ? 'Loading...' : 'No SSH Keys exist for this account.'} +

+ ) : ( + data.map((key, index) => ( + 0 && tw`mt-2`]} + > + +
+

{key.name}

+

SHA256:{key.fingerprint}

+

+ Added on:  + {format(key.createdAt, 'MMM do, yyyy HH:mm')} +

+
+ +
+ )) + )} +
+
+
+ ); +}; diff --git a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx new file mode 100644 index 0000000000..4b4f39cbdf --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Field, Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Input, { Textarea } from '@/components/elements/Input'; +import styled from 'styled-components/macro'; +import { useFlashKey } from '@/plugins/useFlash'; +import { createSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; + +interface Values { + name: string; + publicKey: string; +} + +const CustomTextarea = styled(Textarea)` + ${tw`h-32`} +`; + +export default () => { + const { clearAndAddHttpError } = useFlashKey('account'); + const { mutate } = useSSHKeys(); + + const submit = (values: Values, { setSubmitting, resetForm }: FormikHelpers) => { + clearAndAddHttpError(); + + createSSHKey(values.name, values.publicKey) + .then((key) => { + resetForm(); + mutate((data) => (data || []).concat(key)); + }) + .catch((error) => clearAndAddHttpError(error)) + .then(() => setSubmitting(false)); + }; + + return ( + <> + + {({ isSubmitting }) => ( +
+ + + + + + + +
+ +
+ + )} +
+ + ); +}; diff --git a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx new file mode 100644 index 0000000000..bd9aaf0d55 --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx @@ -0,0 +1,46 @@ +import tw from 'twin.macro'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import React, { useState } from 'react'; +import { useFlashKey } from '@/plugins/useFlash'; +import { deleteSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; +import { Dialog } from '@/components/elements/dialog'; +import Code from '@/components/elements/Code'; + +export default ({ name, fingerprint }: { name: string; fingerprint: string }) => { + const { clearAndAddHttpError } = useFlashKey('account'); + const [visible, setVisible] = useState(false); + const { mutate } = useSSHKeys(); + + const onClick = () => { + clearAndAddHttpError(); + + Promise.all([ + mutate((data) => data?.filter((value) => value.fingerprint !== fingerprint), false), + deleteSSHKey(fingerprint), + ]).catch((error) => { + mutate(undefined, true).catch(console.error); + clearAndAddHttpError(error); + }); + }; + + return ( + <> + setVisible(false)} + > + Removing the {name} SSH key will invalidate its usage across the Panel. + + + + ); +}; diff --git a/resources/scripts/components/elements/AuthenticatedRoute.tsx b/resources/scripts/components/elements/AuthenticatedRoute.tsx new file mode 100644 index 0000000000..2d3b6a6b93 --- /dev/null +++ b/resources/scripts/components/elements/AuthenticatedRoute.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Redirect, Route, RouteProps } from 'react-router'; +import { useStoreState } from '@/state/hooks'; + +export default ({ children, ...props }: Omit) => { + const isAuthenticated = useStoreState((state) => !!state.user.data?.uuid); + + return ( + + isAuthenticated ? children : + } + /> + ); +}; diff --git a/resources/scripts/components/elements/Button.tsx b/resources/scripts/components/elements/Button.tsx index 8577fad7f1..e02f00ff15 100644 --- a/resources/scripts/components/elements/Button.tsx +++ b/resources/scripts/components/elements/Button.tsx @@ -12,88 +12,103 @@ interface Props { const ButtonStyle = styled.button>` ${tw`relative inline-block rounded p-2 uppercase tracking-wide text-sm transition-all duration-150 border`}; - - ${props => ((!props.isSecondary && !props.color) || props.color === 'primary') && css` - ${props => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; - - &:hover:not(:disabled) { - ${tw`bg-primary-600 border-primary-700`}; - } - `}; - - ${props => props.color === 'grey' && css` - ${tw`border-neutral-600 bg-neutral-500 text-neutral-50`}; - - &:hover:not(:disabled) { - ${tw`bg-neutral-600 border-neutral-700`}; - } - `}; - - ${props => props.color === 'green' && css` - ${tw`border-green-600 bg-green-500 text-green-50`}; - - &:hover:not(:disabled) { - ${tw`bg-green-600 border-green-700`}; - } - - ${props => props.isSecondary && css` - &:active:not(:disabled) { + + ${(props) => + ((!props.isSecondary && !props.color) || props.color === 'primary') && + css` + ${(props) => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; + + &:hover:not(:disabled) { + ${tw`bg-primary-600 border-primary-700`}; + } + `}; + + ${(props) => + props.color === 'grey' && + css` + ${tw`border-neutral-600 bg-neutral-500 text-neutral-50`}; + + &:hover:not(:disabled) { + ${tw`bg-neutral-600 border-neutral-700`}; + } + `}; + + ${(props) => + props.color === 'green' && + css` + ${tw`border-green-600 bg-green-500 text-green-50`}; + + &:hover:not(:disabled) { ${tw`bg-green-600 border-green-700`}; } + + ${(props) => + props.isSecondary && + css` + &:active:not(:disabled) { + ${tw`bg-green-600 border-green-700`}; + } + `}; `}; - `}; - - ${props => props.color === 'red' && css` - ${tw`border-red-600 bg-red-500 text-red-50`}; - - &:hover:not(:disabled) { - ${tw`bg-red-600 border-red-700`}; - } - - ${props => props.isSecondary && css` - &:active:not(:disabled) { + + ${(props) => + props.color === 'red' && + css` + ${tw`border-red-600 bg-red-500 text-red-50`}; + + &:hover:not(:disabled) { ${tw`bg-red-600 border-red-700`}; } + + ${(props) => + props.isSecondary && + css` + &:active:not(:disabled) { + ${tw`bg-red-600 border-red-700`}; + } + `}; + `}; + + ${(props) => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; + ${(props) => (!props.size || props.size === 'small') && tw`px-4 py-2`}; + ${(props) => props.size === 'large' && tw`p-4 text-sm`}; + ${(props) => props.size === 'xlarge' && tw`p-4 w-full`}; + + ${(props) => + props.isSecondary && + css` + ${tw`border-neutral-600 bg-transparent text-neutral-200`}; + + &:hover:not(:disabled) { + ${tw`border-neutral-500 text-neutral-100`}; + ${(props) => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; + ${(props) => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; + ${(props) => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; + } `}; - `}; - - ${props => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; - ${props => (!props.size || props.size === 'small') && tw`px-4 py-2`}; - ${props => props.size === 'large' && tw`p-4 text-sm`}; - ${props => props.size === 'xlarge' && tw`p-4 w-full`}; - - ${props => props.isSecondary && css` - ${tw`border-neutral-600 bg-transparent text-neutral-200`}; - - &:hover:not(:disabled) { - ${tw`border-neutral-500 text-neutral-100`}; - ${props => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; - ${props => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; - ${props => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; - } - `}; - - &:disabled { opacity: 0.55; cursor: default } + + &:disabled { + opacity: 0.55; + cursor: default; + } `; type ComponentProps = Omit & Props; const Button: React.FC = ({ children, isLoading, ...props }) => ( - {isLoading && -
- -
- } - - {children} - + {isLoading && ( +
+ +
+ )} + {children}
); type LinkProps = Omit & Props; -const LinkButton: React.FC = props => ; +const LinkButton: React.FC = (props) => ; export { LinkButton, ButtonStyle }; export default Button; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx index dd9d4845fb..8240519368 100644 --- a/resources/scripts/components/elements/Can.tsx +++ b/resources/scripts/components/elements/Can.tsx @@ -14,12 +14,9 @@ const Can = ({ action, matchAny = false, renderOnError, children }: Props) => { return ( <> - { - ((matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p))) ? - children - : - renderOnError - } + {(matchAny && can.filter((p) => p).length > 0) || (!matchAny && can.every((p) => p)) + ? children + : renderOnError} ); }; diff --git a/resources/scripts/components/elements/Checkbox.tsx b/resources/scripts/components/elements/Checkbox.tsx index 7905364895..731fd24de5 100644 --- a/resources/scripts/components/elements/Checkbox.tsx +++ b/resources/scripts/components/elements/Checkbox.tsx @@ -29,7 +29,7 @@ const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => ( type={'checkbox'} checked={(field.value || []).includes(value)} onClick={() => form.setFieldTouched(field.name, true)} - onChange={e => { + onChange={(e) => { const set = new Set(field.value); set.has(value) ? set.delete(value) : set.add(value); diff --git a/resources/scripts/components/elements/Code.tsx b/resources/scripts/components/elements/Code.tsx new file mode 100644 index 0000000000..30eac0d861 --- /dev/null +++ b/resources/scripts/components/elements/Code.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import classNames from 'classnames'; + +interface CodeProps { + dark?: boolean | undefined; + className?: string; + children: React.ReactChild | React.ReactFragment | React.ReactPortal; +} + +export default ({ dark, className, children }: CodeProps) => ( + + {children} + +); diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx index e38ec77a04..cf038d9a0f 100644 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ b/resources/scripts/components/elements/CodemirrorEditor.tsx @@ -98,7 +98,7 @@ const EditorContainer = styled.div` } .CodeMirror-foldmarker { - color: #CBCCC6; + color: #cbccc6; text-shadow: none; margin-left: 0.25rem; margin-right: 0.25rem; @@ -144,7 +144,7 @@ const findModeByFilename = (filename: string) => { }; export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { - const [ editor, setEditor ] = useState(); + const [editor, setEditor] = useState(); const ref = useCallback((node) => { if (!node) return; @@ -169,11 +169,10 @@ export default ({ style, initialContent, filename, mode, fetchContent, onContent autocorrect: false, autocapitalize: false, lint: false, - // This property is actually used, the d.ts file for CodeMirror is incorrect. - // @ts-ignore + // @ts-expect-error this property is actually used, the d.ts file for CodeMirror is incorrect. autoCloseBrackets: true, matchBrackets: true, - gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], }); setEditor(e); @@ -185,15 +184,20 @@ export default ({ style, initialContent, filename, mode, fetchContent, onContent } onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); - }, [ filename ]); + }, [filename]); useEffect(() => { editor && editor.setOption('mode', mode); - }, [ editor, mode ]); + }, [editor, mode]); useEffect(() => { - editor && editor.setValue(initialContent || ''); - }, [ editor, initialContent ]); + if (editor) { + editor.setValue(initialContent || ''); + // Reset the history so that "Ctrl+Z" doesn't delete the intial content + // we just set above. + editor.setHistory({ done: [], undone: [] }); + } + }, [editor, initialContent]); useEffect(() => { if (!editor) { @@ -207,11 +211,11 @@ export default ({ style, initialContent, filename, mode, fetchContent, onContent }); fetchContent(() => Promise.resolve(editor.getValue())); - }, [ editor, fetchContent, onContentSaved ]); + }, [editor, fetchContent, onContentSaved]); return ( -

A description of this Egg.

+
+
+ + +

+ Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. + Required for certain games to work properly when the Node has multiple public IP addresses. +
+ + Enabling this option will disable internal networking for any servers using this egg, + causing them to be unable to internally access other servers on the same node. + +

+
+
- +

The docker images available to servers using this egg. Enter one per line. Users will be able to select from this list of images if more than one value is provided.

@@ -62,6 +72,14 @@

The default startup command that should be used for new servers created with this Egg. You can change this per-server as needed.

+
+ +
+ +

Additional features belonging to the egg. Useful for configuring additional panel modifications.

+
+
@@ -151,5 +169,10 @@ $(this).val(prepend + ' ' + append); } }); + $('#pConfigFeatures').select2({ + tags: true, + selectOnClose: false, + tokenSeparators: [',', ' '], + }); @endsection diff --git a/resources/views/admin/eggs/scripts.blade.php b/resources/views/admin/eggs/scripts.blade.php index 9b82b6d5d1..5bbc9ee3a7 100644 --- a/resources/views/admin/eggs/scripts.blade.php +++ b/resources/views/admin/eggs/scripts.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php index 2441c725f2..fbc14781ec 100644 --- a/resources/views/admin/eggs/variables.blade.php +++ b/resources/views/admin/eggs/variables.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php index 7a235e069c..0f1e6521f4 100644 --- a/resources/views/admin/eggs/view.blade.php +++ b/resources/views/admin/eggs/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -83,9 +78,30 @@
- -

The docker images available to servers using this egg. Enter one per line. Users will be able to select from this list of images if more than one value is provided.

+ +

+ The docker images available to servers using this egg. Enter one per line. Users + will be able to select from this list of images if more than one value is provided. + Optionally, a display name may be provided by prefixing the image with the name + followed by a pipe character, and then the image URL. Example: Display Name|ghcr.io/my/egg +

+
+
+ force_outgoing_ip) checked @endif /> + +

+ Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. + Required for certain games to work properly when the Node has multiple public IP addresses. +
+ + Enabling this option will disable internal networking for any servers using this egg, + causing them to be unable to internally access other servers on the same node. + +

+
+
+
@@ -98,6 +114,17 @@

The default startup command that should be used for new servers using this Egg.

+
+ +
+ +

Additional features belonging to the egg. Useful for configuring additional panel modifications.

+
+
@@ -186,5 +213,10 @@ $(this).val(prepend + ' ' + append); } }); + $('#pConfigFeatures').select2({ + tags: true, + selectOnClose: false, + tokenSeparators: [',', ' '], + }); @endsection diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php index 6c1364a713..9652532e7e 100644 --- a/resources/views/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -34,7 +29,7 @@ @if ($version->isLatestPanel()) You are running Pterodactyl Panel version {{ config('app.version') }}. Your panel is up-to-date! @else - Your panel is not up-to-date! The latest version is
{{ $version->getPanel() }} and you are currently running version {{ config('app.version') }}. + Your panel is not up-to-date! The latest version is {{ $version->getPanel() }} and you are currently running version {{ config('app.version') }}. You can find instructions on how to update your panel here. @endif @@ -49,7 +44,7 @@
 
diff --git a/resources/views/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php index 3d5128db2b..c54ffe0faf 100644 --- a/resources/views/admin/locations/index.blade.php +++ b/resources/views/admin/locations/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/locations/view.blade.php b/resources/views/admin/locations/view.blade.php index 2264ee694c..f34dcf3f5e 100644 --- a/resources/views/admin/locations/view.blade.php +++ b/resources/views/admin/locations/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php index 067fe30a58..a3b989243e 100644 --- a/resources/views/admin/mounts/index.blade.php +++ b/resources/views/admin/mounts/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') diff --git a/resources/views/admin/mounts/view.blade.php b/resources/views/admin/mounts/view.blade.php index f53007f3fd..96a156d244 100644 --- a/resources/views/admin/mounts/view.blade.php +++ b/resources/views/admin/mounts/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') diff --git a/resources/views/admin/nests/index.blade.php b/resources/views/admin/nests/index.blade.php index b45f1187f1..4bb6ae7847 100644 --- a/resources/views/admin/nests/index.blade.php +++ b/resources/views/admin/nests/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nests/new.blade.php b/resources/views/admin/nests/new.blade.php index a93911be72..9a1cb0edc8 100644 --- a/resources/views/admin/nests/new.blade.php +++ b/resources/views/admin/nests/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nests/view.blade.php b/resources/views/admin/nests/view.blade.php index 4dff52504b..9a6730746f 100644 --- a/resources/views/admin/nests/view.blade.php +++ b/resources/views/admin/nests/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -93,7 +88,7 @@ {{ $egg->id }} {{ $egg->name }} - {!! $egg->description !!} + {{ $egg->description }} {{ $egg->servers->count() }} diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php index 524d811074..3b3a3fa48f 100644 --- a/resources/views/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -58,8 +53,8 @@ {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} - {{ $node->memory }} MB - {{ $node->disk }} MB + {{ $node->memory }} MiB + {{ $node->disk }} MiB {{ $node->servers_count }} diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php index 1dcdca7c95..b10d08a762 100644 --- a/resources/views/admin/nodes/new.blade.php +++ b/resources/views/admin/nodes/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -115,7 +110,7 @@
- MB + MiB
@@ -134,7 +129,7 @@
- MB + MiB
diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php index 83ac9c6c60..396428b61b 100644 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ b/resources/views/admin/nodes/view/allocation.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php index 35e3f5f0a6..1bbf15f337 100644 --- a/resources/views/admin/nodes/view/configuration.blade.php +++ b/resources/views/admin/nodes/view/configuration.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index 13d62e9eee..defa0d366e 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -98,7 +93,7 @@
@if($node->maintenance_mode)
-
+
This node is under @@ -112,7 +107,7 @@
Disk Space Allocated - {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} Mb + {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MiB
@@ -124,7 +119,7 @@
Memory Allocated - {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} Mb + {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MiB
@@ -150,14 +145,20 @@ @section('footer-scripts') @parent -@endsection \ No newline at end of file +@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index e152158ce3..4d5ebcdcdf 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 325e61e8eb..4198634f1e 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -175,7 +170,7 @@
- MB + MiB

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -186,7 +181,7 @@
- MB + MiB

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

@@ -199,7 +194,7 @@
- MB + MiB

This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

@@ -343,7 +338,7 @@ function serviceVariablesUpdated(eggId, ids) { // END Persist 'Service Variables' - {!! Theme::js('js/admin/new-server.js?v=20201225') !!} + {!! Theme::js('js/admin/new-server.js?v=20220530') !!} @endif @show - @yield('assets') diff --git a/routes/admin.php b/routes/admin.php index fb45e62945..a1e102eed5 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -1,9 +1,10 @@ name('admin.index'); +Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index'); /* |-------------------------------------------------------------------------- @@ -14,12 +15,12 @@ | */ Route::group(['prefix' => 'api'], function () { - Route::get('/', 'ApiController@index')->name('admin.api.index'); - Route::get('/new', 'ApiController@create')->name('admin.api.new'); + Route::get('/', [Admin\ApiController::class, 'index'])->name('admin.api.index'); + Route::get('/new', [Admin\ApiController::class, 'create'])->name('admin.api.new'); - Route::post('/new', 'ApiController@store'); + Route::post('/new', [Admin\ApiController::class, 'store']); - Route::delete('/revoke/{identifier}', 'ApiController@delete')->name('admin.api.delete'); + Route::delete('/revoke/{identifier}', [Admin\ApiController::class, 'delete'])->name('admin.api.delete'); }); /* @@ -31,11 +32,11 @@ | */ Route::group(['prefix' => 'locations'], function () { - Route::get('/', 'LocationController@index')->name('admin.locations'); - Route::get('/view/{location}', 'LocationController@view')->name('admin.locations.view'); + Route::get('/', [Admin\LocationController::class, 'index'])->name('admin.locations'); + Route::get('/view/{location:id}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); - Route::post('/', 'LocationController@create'); - Route::patch('/view/{location}', 'LocationController@update'); + Route::post('/', [Admin\LocationController::class, 'create']); + Route::patch('/view/{location:id}', [Admin\LocationController::class, 'update']); }); /* @@ -47,12 +48,12 @@ | */ Route::group(['prefix' => 'databases'], function () { - Route::get('/', 'DatabaseController@index')->name('admin.databases'); - Route::get('/view/{host}', 'DatabaseController@view')->name('admin.databases.view'); + Route::get('/', [Admin\DatabaseController::class, 'index'])->name('admin.databases'); + Route::get('/view/{host:id}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); - Route::post('/', 'DatabaseController@create'); - Route::patch('/view/{host}', 'DatabaseController@update'); - Route::delete('/view/{host}', 'DatabaseController@delete'); + Route::post('/', [Admin\DatabaseController::class, 'create']); + Route::patch('/view/{host:id}', [Admin\DatabaseController::class, 'update']); + Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']); }); /* @@ -64,14 +65,15 @@ | */ Route::group(['prefix' => 'settings'], function () { - Route::get('/', 'Settings\IndexController@index')->name('admin.settings'); - Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail'); - Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced'); - Route::post('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test'); - - Route::patch('/', 'Settings\IndexController@update'); - Route::patch('/mail', 'Settings\MailController@update'); - Route::patch('/advanced', 'Settings\AdvancedController@update'); + Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings'); + Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail'); + Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced'); + + Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test'); + + Route::patch('/', [Admin\Settings\IndexController::class, 'update']); + Route::patch('/mail', [Admin\Settings\MailController::class, 'update']); + Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']); }); /* @@ -83,15 +85,15 @@ | */ Route::group(['prefix' => 'users'], function () { - Route::get('/', 'UserController@index')->name('admin.users'); - Route::get('/accounts.json', 'UserController@json')->name('admin.users.json'); - Route::get('/new', 'UserController@create')->name('admin.users.new'); - Route::get('/view/{user}', 'UserController@view')->name('admin.users.view'); + Route::get('/', [Admin\UserController::class, 'index'])->name('admin.users'); + Route::get('/accounts.json', [Admin\UserController::class, 'json'])->name('admin.users.json'); + Route::get('/new', [Admin\UserController::class, 'create'])->name('admin.users.new'); + Route::get('/view/{user:id}', [Admin\UserController::class, 'view'])->name('admin.users.view'); - Route::post('/new', 'UserController@store'); - Route::patch('/view/{user}', 'UserController@update'); + Route::post('/new', [Admin\UserController::class, 'store']); - Route::delete('/view/{user}', 'UserController@delete'); + Route::patch('/view/{user:id}', [Admin\UserController::class, 'update']); + Route::delete('/view/{user:id}', [Admin\UserController::class, 'delete'])->name('admin.users.delete'); }); /* @@ -103,37 +105,38 @@ | */ Route::group(['prefix' => 'servers'], function () { - Route::get('/', 'Servers\ServerController@index')->name('admin.servers'); - Route::get('/new', 'Servers\CreateServerController@index')->name('admin.servers.new'); - Route::get('/view/{server}', 'Servers\ServerViewController@index')->name('admin.servers.view'); + Route::get('/', [Admin\Servers\ServerController::class, 'index'])->name('admin.servers'); + Route::get('/new', [Admin\Servers\CreateServerController::class, 'index'])->name('admin.servers.new'); + Route::get('/view/{server:id}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); Route::group(['middleware' => [ServerInstalled::class]], function () { - Route::get('/view/{server}/details', 'Servers\ServerViewController@details')->name('admin.servers.view.details'); - Route::get('/view/{server}/build', 'Servers\ServerViewController@build')->name('admin.servers.view.build'); - Route::get('/view/{server}/startup', 'Servers\ServerViewController@startup')->name('admin.servers.view.startup'); - Route::get('/view/{server}/database', 'Servers\ServerViewController@database')->name('admin.servers.view.database'); - Route::get('/view/{server}/mounts', 'Servers\ServerViewController@mounts')->name('admin.servers.view.mounts'); + Route::get('/view/{server:id}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); + Route::get('/view/{server:id}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); + Route::get('/view/{server:id}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); + Route::get('/view/{server:id}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); + Route::get('/view/{server:id}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); }); - Route::get('/view/{server}/manage', 'Servers\ServerViewController@manage')->name('admin.servers.view.manage'); - Route::get('/view/{server}/delete', 'Servers\ServerViewController@delete')->name('admin.servers.view.delete'); - - Route::post('/new', 'Servers\CreateServerController@store'); - Route::post('/view/{server}/build', 'ServersController@updateBuild'); - Route::post('/view/{server}/startup', 'ServersController@saveStartup'); - Route::post('/view/{server}/database', 'ServersController@newDatabase'); - Route::post('/view/{server}/mounts/{mount}', 'ServersController@addMount')->name('admin.servers.view.mounts.toggle'); - Route::post('/view/{server}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension'); - Route::post('/view/{server}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{server}/manage/transfer', 'Servers\ServerTransferController@transfer')->name('admin.servers.view.manage.transfer'); - Route::post('/view/{server}/delete', 'ServersController@delete'); - - Route::patch('/view/{server}/details', 'ServersController@setDetails'); - Route::patch('/view/{server}/database', 'ServersController@resetDatabasePassword'); - - Route::delete('/view/{server}/database/{database}/delete', 'ServersController@deleteDatabase')->name('admin.servers.view.database.delete'); - Route::delete('/view/{server}/mounts/{mount}', 'ServersController@deleteMount'); + Route::get('/view/{server:id}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); + Route::get('/view/{server:id}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); + + Route::post('/new', [Admin\Servers\CreateServerController::class, 'store']); + Route::post('/view/{server:id}/build', [Admin\ServersController::class, 'updateBuild']); + Route::post('/view/{server:id}/startup', [Admin\ServersController::class, 'saveStartup']); + Route::post('/view/{server:id}/database', [Admin\ServersController::class, 'newDatabase']); + Route::post('/view/{server:id}/mounts', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.store'); + Route::post('/view/{server:id}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); + Route::post('/view/{server:id}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); + Route::post('/view/{server:id}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); + Route::post('/view/{server:id}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); + Route::post('/view/{server:id}/delete', [Admin\ServersController::class, 'delete']); + + Route::patch('/view/{server:id}/details', [Admin\ServersController::class, 'setDetails']); + Route::patch('/view/{server:id}/database', [Admin\ServersController::class, 'resetDatabasePassword']); + + Route::delete('/view/{server:id}/database/{database:id}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); + Route::delete('/view/{server:id}/mounts/{mount:id}', [Admin\ServersController::class, 'deleteMount']) + ->name('admin.servers.view.mounts.delete'); }); /* @@ -145,26 +148,26 @@ | */ Route::group(['prefix' => 'nodes'], function () { - Route::get('/', 'Nodes\NodeController@index')->name('admin.nodes'); - Route::get('/new', 'NodesController@create')->name('admin.nodes.new'); - Route::get('/view/{node}', 'Nodes\NodeViewController@index')->name('admin.nodes.view'); - Route::get('/view/{node}/settings', 'Nodes\NodeViewController@settings')->name('admin.nodes.view.settings'); - Route::get('/view/{node}/configuration', 'Nodes\NodeViewController@configuration')->name('admin.nodes.view.configuration'); - Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation'); - Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers'); - Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController'); - - Route::post('/new', 'NodesController@store'); - Route::post('/view/{node}/allocation', 'NodesController@createAllocation'); - Route::post('/view/{node}/allocation/remove', 'NodesController@allocationRemoveBlock')->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{node}/allocation/alias', 'NodesController@allocationSetAlias')->name('admin.nodes.view.allocation.setAlias'); - Route::post('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token'); - - Route::patch('/view/{node}/settings', 'NodesController@updateSettings'); - - Route::delete('/view/{node}/delete', 'NodesController@delete')->name('admin.nodes.view.delete'); - Route::delete('/view/{node}/allocation/remove/{allocation}', 'NodesController@allocationRemoveSingle')->name('admin.nodes.view.allocation.removeSingle'); - Route::delete('/view/{node}/allocations', 'NodesController@allocationRemoveMultiple')->name('admin.nodes.view.allocation.removeMultiple'); + Route::get('/', [Admin\Nodes\NodeController::class, 'index'])->name('admin.nodes'); + Route::get('/new', [Admin\NodesController::class, 'create'])->name('admin.nodes.new'); + Route::get('/view/{node:id}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); + Route::get('/view/{node:id}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); + Route::get('/view/{node:id}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); + Route::get('/view/{node:id}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); + Route::get('/view/{node:id}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); + Route::get('/view/{node:id}/system-information', Admin\Nodes\SystemInformationController::class); + + Route::post('/new', [Admin\NodesController::class, 'store']); + Route::post('/view/{node:id}/allocation', [Admin\NodesController::class, 'createAllocation']); + Route::post('/view/{node:id}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); + Route::post('/view/{node:id}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); + Route::post('/view/{node:id}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); + + Route::patch('/view/{node:id}/settings', [Admin\NodesController::class, 'updateSettings']); + + Route::delete('/view/{node:id}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); + Route::delete('/view/{node:id}/allocation/remove/{allocation:id}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); + Route::delete('/view/{node:id}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); }); /* @@ -176,17 +179,17 @@ | */ Route::group(['prefix' => 'mounts'], function () { - Route::get('/', 'MountController@index')->name('admin.mounts'); - Route::get('/view/{mount}', 'MountController@view')->name('admin.mounts.view'); + Route::get('/', [Admin\MountController::class, 'index'])->name('admin.mounts'); + Route::get('/view/{mount:id}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); - Route::post('/', 'MountController@create'); - Route::post('/{mount}/eggs', 'MountController@addEggs')->name('admin.mounts.eggs'); - Route::post('/{mount}/nodes', 'MountController@addNodes')->name('admin.mounts.nodes'); + Route::post('/', [Admin\MountController::class, 'create']); + Route::post('/{mount:id}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); + Route::post('/{mount:id}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); - Route::patch('/view/{mount}', 'MountController@update'); + Route::patch('/view/{mount:id}', [Admin\MountController::class, 'update']); - Route::delete('/{mount}/eggs/{egg_id}', 'MountController@deleteEgg'); - Route::delete('/{mount}/nodes/{node_id}', 'MountController@deleteNode'); + Route::delete('/{mount:id}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); + Route::delete('/{mount:id}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); }); /* @@ -198,28 +201,28 @@ | */ Route::group(['prefix' => 'nests'], function () { - Route::get('/', 'Nests\NestController@index')->name('admin.nests'); - Route::get('/new', 'Nests\NestController@create')->name('admin.nests.new'); - Route::get('/view/{nest}', 'Nests\NestController@view')->name('admin.nests.view'); - Route::get('/egg/new', 'Nests\EggController@create')->name('admin.nests.egg.new'); - Route::get('/egg/{egg}', 'Nests\EggController@view')->name('admin.nests.egg.view'); - Route::get('/egg/{egg}/export', 'Nests\EggShareController@export')->name('admin.nests.egg.export'); - Route::get('/egg/{egg}/variables', 'Nests\EggVariableController@view')->name('admin.nests.egg.variables'); - Route::get('/egg/{egg}/scripts', 'Nests\EggScriptController@index')->name('admin.nests.egg.scripts'); - - Route::post('/new', 'Nests\NestController@store'); - Route::post('/import', 'Nests\EggShareController@import')->name('admin.nests.egg.import'); - Route::post('/egg/new', 'Nests\EggController@store'); - Route::post('/egg/{egg}/variables', 'Nests\EggVariableController@store'); - - Route::put('/egg/{egg}', 'Nests\EggShareController@update'); - - Route::patch('/view/{nest}', 'Nests\NestController@update'); - Route::patch('/egg/{egg}', 'Nests\EggController@update'); - Route::patch('/egg/{egg}/scripts', 'Nests\EggScriptController@update'); - Route::patch('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@update')->name('admin.nests.egg.variables.edit'); - - Route::delete('/view/{nest}', 'Nests\NestController@destroy'); - Route::delete('/egg/{egg}', 'Nests\EggController@destroy'); - Route::delete('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@destroy'); + Route::get('/', [Admin\Nests\NestController::class, 'index'])->name('admin.nests'); + Route::get('/new', [Admin\Nests\NestController::class, 'create'])->name('admin.nests.new'); + Route::get('/view/{nest:id}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); + Route::get('/egg/new', [Admin\Nests\EggController::class, 'create'])->name('admin.nests.egg.new'); + Route::get('/egg/{egg:id}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); + Route::get('/egg/{egg:id}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); + Route::get('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); + Route::get('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); + + Route::post('/new', [Admin\Nests\NestController::class, 'store']); + Route::post('/import', [Admin\Nests\EggShareController::class, 'import'])->name('admin.nests.egg.import'); + Route::post('/egg/new', [Admin\Nests\EggController::class, 'store']); + Route::post('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'store']); + + Route::put('/egg/{egg:id}', [Admin\Nests\EggShareController::class, 'update']); + + Route::patch('/view/{nest:id}', [Admin\Nests\NestController::class, 'update']); + Route::patch('/egg/{egg:id}', [Admin\Nests\EggController::class, 'update']); + Route::patch('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'update']); + Route::patch('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); + + Route::delete('/view/{nest:id}', [Admin\Nests\NestController::class, 'destroy']); + Route::delete('/egg/{egg:id}', [Admin\Nests\EggController::class, 'destroy']); + Route::delete('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'destroy']); }); diff --git a/routes/api-application.php b/routes/api-application.php index 160f5f8b03..f73096aa82 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -1,6 +1,7 @@ '/users'], function () { - Route::get('/', 'Users\UserController@index')->name('api.application.users'); - Route::get('/{user}', 'Users\UserController@view')->name('api.application.users.view'); - Route::get('/external/{external_id}', 'Users\ExternalUserController@index')->name('api.application.users.external'); + Route::get('/', [Application\Users\UserController::class, 'index'])->name('api.application.users'); + Route::get('/{user:id}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); + Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index'])->name('api.application.users.external'); - Route::post('/', 'Users\UserController@store'); - Route::patch('/{user}', 'Users\UserController@update'); + Route::post('/', [Application\Users\UserController::class, 'store']); + Route::patch('/{user:id}', [Application\Users\UserController::class, 'update']); - Route::delete('/{user}', 'Users\UserController@delete'); + Route::delete('/{user:id}', [Application\Users\UserController::class, 'delete']); }); /* @@ -31,20 +32,20 @@ | */ Route::group(['prefix' => '/nodes'], function () { - Route::get('/', 'Nodes\NodeController@index')->name('api.application.nodes'); - Route::get('/deployable', 'Nodes\NodeDeploymentController'); - Route::get('/{node}', 'Nodes\NodeController@view')->name('api.application.nodes.view'); - Route::get('/{node}/configuration', 'Nodes\NodeConfigurationController'); + Route::get('/', [Application\Nodes\NodeController::class, 'index'])->name('api.application.nodes'); + Route::get('/deployable', Application\Nodes\NodeDeploymentController::class); + Route::get('/{node:id}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); + Route::get('/{node:id}/configuration', Application\Nodes\NodeConfigurationController::class); - Route::post('/', 'Nodes\NodeController@store'); - Route::patch('/{node}', 'Nodes\NodeController@update'); + Route::post('/', [Application\Nodes\NodeController::class, 'store']); + Route::patch('/{node:id}', [Application\Nodes\NodeController::class, 'update'])->name('api.application.nodes.update'); - Route::delete('/{node}', 'Nodes\NodeController@delete'); + Route::delete('/{node:id}', [Application\Nodes\NodeController::class, 'delete']); - Route::group(['prefix' => '/{node}/allocations'], function () { - Route::get('/', 'Nodes\AllocationController@index')->name('api.application.allocations'); - Route::post('/', 'Nodes\AllocationController@store'); - Route::delete('/{allocation}', 'Nodes\AllocationController@delete')->name('api.application.allocations.view'); + Route::group(['prefix' => '/{node:id}/allocations'], function () { + Route::get('/', [Application\Nodes\AllocationController::class, 'index'])->name('api.application.allocations'); + Route::post('/', [Application\Nodes\AllocationController::class, 'store']); + Route::delete('/{allocation:id}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); }); }); @@ -57,13 +58,13 @@ | */ Route::group(['prefix' => '/locations'], function () { - Route::get('/', 'Locations\LocationController@index')->name('api.applications.locations'); - Route::get('/{location}', 'Locations\LocationController@view')->name('api.application.locations.view'); + Route::get('/', [Application\Locations\LocationController::class, 'index'])->name('api.applications.locations'); + Route::get('/{location:id}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); - Route::post('/', 'Locations\LocationController@store'); - Route::patch('/{location}', 'Locations\LocationController@update'); + Route::post('/', [Application\Locations\LocationController::class, 'store']); + Route::patch('/{location:id}', [Application\Locations\LocationController::class, 'update']); - Route::delete('/{location}', 'Locations\LocationController@delete'); + Route::delete('/{location:id}', [Application\Locations\LocationController::class, 'delete']); }); /* @@ -75,31 +76,31 @@ | */ Route::group(['prefix' => '/servers'], function () { - Route::get('/', 'Servers\ServerController@index')->name('api.application.servers'); - Route::get('/{server}', 'Servers\ServerController@view')->name('api.application.servers.view'); - Route::get('/external/{external_id}', 'Servers\ExternalServerController@index')->name('api.application.servers.external'); + Route::get('/', [Application\Servers\ServerController::class, 'index'])->name('api.application.servers'); + Route::get('/{server:id}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); + Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index'])->name('api.application.servers.external'); - Route::patch('/{server}/details', 'Servers\ServerDetailsController@details')->name('api.application.servers.details'); - Route::patch('/{server}/build', 'Servers\ServerDetailsController@build')->name('api.application.servers.build'); - Route::patch('/{server}/startup', 'Servers\StartupController@index')->name('api.application.servers.startup'); + Route::patch('/{server:id}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); + Route::patch('/{server:id}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); + Route::patch('/{server:id}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); - Route::post('/', 'Servers\ServerController@store'); - Route::post('/{server}/suspend', 'Servers\ServerManagementController@suspend')->name('api.application.servers.suspend'); - Route::post('/{server}/unsuspend', 'Servers\ServerManagementController@unsuspend')->name('api.application.servers.unsuspend'); - Route::post('/{server}/reinstall', 'Servers\ServerManagementController@reinstall')->name('api.application.servers.reinstall'); + Route::post('/', [Application\Servers\ServerController::class, 'store']); + Route::post('/{server:id}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); + Route::post('/{server:id}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); + Route::post('/{server:id}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); - Route::delete('/{server}', 'Servers\ServerController@delete'); - Route::delete('/{server}/{force?}', 'Servers\ServerController@delete'); + Route::delete('/{server:id}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server:id}/{force?}', [Application\Servers\ServerController::class, 'delete']); // Database Management Endpoint - Route::group(['prefix' => '/{server}/databases'], function () { - Route::get('/', 'Servers\DatabaseController@index')->name('api.application.servers.databases'); - Route::get('/{database}', 'Servers\DatabaseController@view')->name('api.application.servers.databases.view'); + Route::group(['prefix' => '/{server:id}/databases'], function () { + Route::get('/', [Application\Servers\DatabaseController::class, 'index'])->name('api.application.servers.databases'); + Route::get('/{database:id}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); - Route::post('/', 'Servers\DatabaseController@store'); - Route::post('/{database}/reset-password', 'Servers\DatabaseController@resetPassword'); + Route::post('/', [Application\Servers\DatabaseController::class, 'store']); + Route::post('/{database:id}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); - Route::delete('/{database}', 'Servers\DatabaseController@delete'); + Route::delete('/{database:id}', [Application\Servers\DatabaseController::class, 'delete']); }); }); @@ -112,12 +113,12 @@ | */ Route::group(['prefix' => '/nests'], function () { - Route::get('/', 'Nests\NestController@index')->name('api.application.nests'); - Route::get('/{nest}', 'Nests\NestController@view')->name('api.application.nests.view'); + Route::get('/', [Application\Nests\NestController::class, 'index'])->name('api.application.nests'); + Route::get('/{nest:id}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); // Egg Management Endpoint - Route::group(['prefix' => '/{nest}/eggs'], function () { - Route::get('/', 'Nests\EggController@index')->name('api.application.nests.eggs'); - Route::get('/{egg}', 'Nests\EggController@view')->name('api.application.nests.eggs.view'); + Route::group(['prefix' => '/{nest:id}/eggs'], function () { + Route::get('/', [Application\Nests\EggController::class, 'index'])->name('api.application.nests.eggs'); + Route::get('/{egg:id}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); }); }); diff --git a/routes/api-client.php b/routes/api-client.php index ec4988bc8b..b5595ec01b 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -1,6 +1,10 @@ name('api:client.index'); -Route::get('/permissions', 'ClientController@permissions'); +Route::get('/', [Client\ClientController::class, 'index'])->name('api:client.index'); +Route::get('/permissions', [Client\ClientController::class, 'permissions']); + +Route::prefix('/account')->middleware(AccountSubject::class)->group(function () { + Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () { + Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account'); + Route::get('/two-factor', [Client\TwoFactorController::class, 'index']); + Route::post('/two-factor', [Client\TwoFactorController::class, 'store']); + Route::post('/two-factor/disable', [Client\TwoFactorController::class, 'delete']); + }); + + Route::put('/email', [Client\AccountController::class, 'updateEmail']) + ->middleware('throttle') + ->name('api:client.account.update-email'); + Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); -Route::group(['prefix' => '/account'], function () { - Route::get('/', 'AccountController@index')->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::get('/two-factor', 'TwoFactorController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::post('/two-factor', 'TwoFactorController@store')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::delete('/two-factor', 'TwoFactorController@delete')->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::get('/activity', Client\ActivityLogController::class)->name('api:client.account.activity'); - Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email'); - Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password'); + Route::get('/api-keys', [Client\ApiKeyController::class, 'index']); + Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); + Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); - Route::get('/api-keys', 'ApiKeyController@index'); - Route::post('/api-keys', 'ApiKeyController@store'); - Route::delete('/api-keys/{identifier}', 'ApiKeyController@delete'); + Route::prefix('/ssh-keys')->group(function () { + Route::get('/', [Client\SSHKeyController::class, 'index']); + Route::post('/', [Client\SSHKeyController::class, 'store']); + Route::post('/remove', [Client\SSHKeyController::class, 'delete']); + }); }); /* @@ -38,84 +54,100 @@ | Endpoint: /api/client/servers/{server} | */ -Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { - Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view'); - Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws'); - Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources'); - - Route::post('/command', 'Servers\CommandController@index'); - Route::post('/power', 'Servers\PowerController@index'); +Route::group([ + 'prefix' => '/servers/{server}', + 'middleware' => [ + ServerSubject::class, + AuthenticateServerAccess::class, + ResourceBelongsToServer::class, + ], +], function () { + Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view'); + Route::middleware([ResourceLimit::Websocket->middleware()]) + ->get('/websocket', Client\Servers\WebsocketController::class) + ->name('api:client:server.ws'); + Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources'); + Route::get('/activity', Client\Servers\ActivityLogController::class)->name('api:client:server.activity'); + + Route::post('/command', [Client\Servers\CommandController::class, 'index']); + Route::post('/power', [Client\Servers\PowerController::class, 'index']); Route::group(['prefix' => '/databases'], function () { - Route::get('/', 'Servers\DatabaseController@index'); - Route::post('/', 'Servers\DatabaseController@store'); - Route::post('/{database}/rotate-password', 'Servers\DatabaseController@rotatePassword'); - Route::delete('/{database}', 'Servers\DatabaseController@delete'); + Route::get('/', [Client\Servers\DatabaseController::class, 'index']); + Route::middleware([ResourceLimit::Database->middleware()]) + ->post('/', [Client\Servers\DatabaseController::class, 'store']); + Route::post('/{database}/rotate-password', [Client\Servers\DatabaseController::class, 'rotatePassword']); + Route::delete('/{database}', [Client\Servers\DatabaseController::class, 'delete']); }); Route::group(['prefix' => '/files'], function () { - Route::get('/list', 'Servers\FileController@directory'); - Route::get('/contents', 'Servers\FileController@contents'); - Route::get('/download', 'Servers\FileController@download'); - Route::put('/rename', 'Servers\FileController@rename'); - Route::post('/copy', 'Servers\FileController@copy'); - Route::post('/write', 'Servers\FileController@write'); - Route::post('/compress', 'Servers\FileController@compress'); - Route::post('/decompress', 'Servers\FileController@decompress'); - Route::post('/delete', 'Servers\FileController@delete'); - Route::post('/create-folder', 'Servers\FileController@create'); - Route::post('/chmod', 'Servers\FileController@chmod'); - Route::post('/pull', 'Servers\FileController@pull')->middleware(['throttle:10,5']); - Route::get('/upload', 'Servers\FileUploadController'); + Route::get('/list', [Client\Servers\FileController::class, 'directory']); + Route::get('/contents', [Client\Servers\FileController::class, 'contents']); + Route::get('/download', [Client\Servers\FileController::class, 'download']); + Route::put('/rename', [Client\Servers\FileController::class, 'rename']); + Route::post('/copy', [Client\Servers\FileController::class, 'copy']); + Route::post('/write', [Client\Servers\FileController::class, 'write']); + Route::post('/compress', [Client\Servers\FileController::class, 'compress']); + Route::post('/decompress', [Client\Servers\FileController::class, 'decompress']); + Route::post('/delete', [Client\Servers\FileController::class, 'delete']); + Route::post('/create-folder', [Client\Servers\FileController::class, 'create']); + Route::post('/chmod', [Client\Servers\FileController::class, 'chmod']); + Route::middleware([ResourceLimit::FilePull->middleware()]) + ->post('/pull', [Client\Servers\FileController::class, 'pull']); + Route::get('/upload', Client\Servers\FileUploadController::class); }); Route::group(['prefix' => '/schedules'], function () { - Route::get('/', 'Servers\ScheduleController@index'); - Route::post('/', 'Servers\ScheduleController@store'); - Route::get('/{schedule}', 'Servers\ScheduleController@view'); - Route::post('/{schedule}', 'Servers\ScheduleController@update'); - Route::post('/{schedule}/execute', 'Servers\ScheduleController@execute'); - Route::delete('/{schedule}', 'Servers\ScheduleController@delete'); - - Route::post('/{schedule}/tasks', 'Servers\ScheduleTaskController@store'); - Route::post('/{schedule}/tasks/{task}', 'Servers\ScheduleTaskController@update'); - Route::delete('/{schedule}/tasks/{task}', 'Servers\ScheduleTaskController@delete'); + Route::get('/', [Client\Servers\ScheduleController::class, 'index']); + Route::middleware([ResourceLimit::Schedule->middleware()]) + ->post('/', [Client\Servers\ScheduleController::class, 'store']); + Route::get('/{schedule}', [Client\Servers\ScheduleController::class, 'view']); + Route::post('/{schedule}', [Client\Servers\ScheduleController::class, 'update']); + Route::post('/{schedule}/execute', [Client\Servers\ScheduleController::class, 'execute']); + Route::delete('/{schedule}', [Client\Servers\ScheduleController::class, 'delete']); + + Route::post('/{schedule}/tasks', [Client\Servers\ScheduleTaskController::class, 'store']); + Route::post('/{schedule}/tasks/{task}', [Client\Servers\ScheduleTaskController::class, 'update']); + Route::delete('/{schedule}/tasks/{task}', [Client\Servers\ScheduleTaskController::class, 'delete']); }); Route::group(['prefix' => '/network'], function () { - Route::get('/allocations', 'Servers\NetworkAllocationController@index'); - Route::post('/allocations', 'Servers\NetworkAllocationController@store'); - Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update'); - Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary'); - Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete'); + Route::get('/allocations', [Client\Servers\NetworkAllocationController::class, 'index']); + Route::middleware([ResourceLimit::Allocation->middleware()]) + ->post('/allocations', [Client\Servers\NetworkAllocationController::class, 'store']); + Route::post('/allocations/{allocation}', [Client\Servers\NetworkAllocationController::class, 'update']); + Route::post('/allocations/{allocation}/primary', [Client\Servers\NetworkAllocationController::class, 'setPrimary']); + Route::delete('/allocations/{allocation}', [Client\Servers\NetworkAllocationController::class, 'delete']); }); Route::group(['prefix' => '/users'], function () { - Route::get('/', 'Servers\SubuserController@index'); - Route::post('/', 'Servers\SubuserController@store'); - Route::get('/{user}', 'Servers\SubuserController@view'); - Route::post('/{user}', 'Servers\SubuserController@update'); - Route::delete('/{user}', 'Servers\SubuserController@delete'); + Route::get('/', [Client\Servers\SubuserController::class, 'index']); + Route::middleware([ResourceLimit::Subuser->middleware()]) + ->post('/', [Client\Servers\SubuserController::class, 'store']); + Route::get('/{user}', [Client\Servers\SubuserController::class, 'view']); + Route::post('/{user}', [Client\Servers\SubuserController::class, 'update']); + Route::delete('/{user}', [Client\Servers\SubuserController::class, 'delete']); }); Route::group(['prefix' => '/backups'], function () { - Route::get('/', 'Servers\BackupController@index'); - Route::post('/', 'Servers\BackupController@store'); - Route::get('/{backup}', 'Servers\BackupController@view'); - Route::get('/{backup}/download', 'Servers\BackupController@download'); - Route::post('/{backup}/lock', 'Servers\BackupController@toggleLock'); - Route::post('/{backup}/restore', 'Servers\BackupController@restore'); - Route::delete('/{backup}', 'Servers\BackupController@delete'); + Route::get('/', [Client\Servers\BackupController::class, 'index']); + Route::post('/', [Client\Servers\BackupController::class, 'store']); + Route::get('/{backup}', [Client\Servers\BackupController::class, 'view']); + Route::get('/{backup}/download', [Client\Servers\BackupController::class, 'download']); + Route::post('/{backup}/lock', [Client\Servers\BackupController::class, 'toggleLock']); + Route::middleware([ResourceLimit::Backup->middleware()]) + ->post('/{backup}/restore', [Client\Servers\BackupController::class, 'restore']); + Route::delete('/{backup}', [Client\Servers\BackupController::class, 'delete']); }); Route::group(['prefix' => '/startup'], function () { - Route::get('/', 'Servers\StartupController@index'); - Route::put('/variable', 'Servers\StartupController@update'); + Route::get('/', [Client\Servers\StartupController::class, 'index']); + Route::put('/variable', [Client\Servers\StartupController::class, 'update']); }); Route::group(['prefix' => '/settings'], function () { - Route::post('/rename', 'Servers\SettingsController@rename'); - Route::post('/reinstall', 'Servers\SettingsController@reinstall'); - Route::put('/docker-image', 'Servers\SettingsController@dockerImage'); + Route::post('/rename', [Client\Servers\SettingsController::class, 'rename']); + Route::post('/reinstall', [Client\Servers\SettingsController::class, 'reinstall']); + Route::put('/docker-image', [Client\Servers\SettingsController::class, 'dockerImage']); }); }); diff --git a/routes/api-remote.php b/routes/api-remote.php index 3d4839324f..7db442a54c 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -1,25 +1,25 @@ '/servers/{uuid}'], function () { - Route::get('/', 'Servers\ServerDetailsController'); - Route::get('/install', 'Servers\ServerInstallController@index'); - Route::post('/install', 'Servers\ServerInstallController@store'); - - Route::post('/archive', 'Servers\ServerTransferController@archive'); - Route::get('/transfer/failure', 'Servers\ServerTransferController@failure'); - Route::get('/transfer/success', 'Servers\ServerTransferController@success'); + Route::get('/', Remote\Servers\ServerDetailsController::class); + Route::get('/install', [Remote\Servers\ServerInstallController::class, 'index']); + Route::post('/install', [Remote\Servers\ServerInstallController::class, 'store']); + Route::post('/transfer/failure', [Remote\Servers\ServerTransferController::class, 'failure']); + Route::post('/transfer/success', [Remote\Servers\ServerTransferController::class, 'success']); }); Route::group(['prefix' => '/backups'], function () { - Route::get('/{backup}', 'Backups\BackupRemoteUploadController'); - Route::post('/{backup}', 'Backups\BackupStatusController@index'); - Route::post('/{backup}/restore', 'Backups\BackupStatusController@restore'); + Route::get('/{backup}', Remote\Backups\BackupRemoteUploadController::class); + Route::post('/{backup}', [Remote\Backups\BackupStatusController::class, 'index']); + Route::post('/{backup}/restore', [Remote\Backups\BackupStatusController::class, 'restore']); }); diff --git a/routes/auth.php b/routes/auth.php index 0acd9fded5..36039f3a2c 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,5 +1,8 @@ 'guest'], function () { - // These routes are defined so that we can continue to reference them programatically. - // They all route to the same controller function which passes off to Vuejs. - Route::get('/login', 'LoginController@index')->name('auth.login'); - Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); - Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); - - // Apply a throttle to authentication action endpoints, in addition to the - // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ - // - // @see \Pterodactyl\Providers\RouteServiceProvider - Route::middleware(['throttle:authentication'])->group(function () { - // Login endpoints. - Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); - - // Forgot password route. A post to this endpoint will trigger an - // email to be sent containing a reset token. - Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail') - ->name('auth.post.forgot-password') - ->middleware('recaptcha'); - }); - - // Password reset routes. This endpoint is hit after going through - // the forgot password routes to acquire a token (or after an account - // is created). - Route::post('/password/reset', 'ResetPasswordController')->name('auth.reset-password'); - - // Catch any other combinations of routes and pass them off to the Vuejs component. - Route::fallback('LoginController@index'); + +// These routes are defined so that we can continue to reference them programmatically. +// They all route to the same controller function which passes off to React. +Route::get('/login', [Auth\LoginController::class, 'index'])->name('auth.login'); +Route::get('/password', [Auth\LoginController::class, 'index'])->name('auth.forgot-password'); +Route::get('/password/reset/{token}', [Auth\LoginController::class, 'index'])->name('auth.reset'); + +// Apply a throttle to authentication action endpoints, in addition to the +// recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ +// +// @see \Pterodactyl\Providers\RouteServiceProvider +Route::middleware(['throttle:authentication'])->group(function () { + // Login endpoints. + Route::post('/login', [Auth\LoginController::class, 'login'])->middleware('recaptcha'); + Route::post('/login/checkpoint', Auth\LoginCheckpointController::class)->name('auth.login-checkpoint'); + + // Forgot password route. A post to this endpoint will trigger an + // email to be sent containing a reset token. + Route::post('/password', [Auth\ForgotPasswordController::class, 'sendResetLinkEmail']) + ->name('auth.post.forgot-password') + ->middleware('recaptcha'); }); -/* -|-------------------------------------------------------------------------- -| Routes Accessible only when logged in -|-------------------------------------------------------------------------- -| -| Endpoint: /auth -| -*/ -Route::post('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth', 'csrf'); +// Password reset routes. This endpoint is hit after going through +// the forgot password routes to acquire a token (or after an account +// is created). +Route::post('/password/reset', Auth\ResetPasswordController::class)->name('auth.reset-password'); + +// Remove the guest middleware and apply the authenticated middleware to this endpoint, +// so it cannot be used unless you're already logged in. +Route::post('/logout', [Auth\LoginController::class, 'logout']) + ->withoutMiddleware('guest') + ->middleware('auth') + ->name('auth.logout'); + +// Catch any other combinations of routes and pass them off to the React component. +Route::fallback([Auth\LoginController::class, 'index']); diff --git a/routes/base.php b/routes/base.php index 3be62423e5..93f45b577e 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,15 +1,17 @@ name('index')->fallback(); -Route::get('/account', 'IndexController@index') +Route::get('/', [Base\IndexController::class, 'index'])->name('index')->fallback(); +Route::get('/account', [Base\IndexController::class, 'index']) ->withoutMiddleware(RequireTwoFactorAuthentication::class) ->name('account'); -Route::get('/locales/{locale}/{namespace}.json', 'LocaleController') - ->withoutMiddleware(RequireTwoFactorAuthentication::class) +Route::get('/locales/locale.json', Base\LocaleController::class) + ->withoutMiddleware(['auth', RequireTwoFactorAuthentication::class]) ->where('namespace', '.*'); -Route::get('/{react}', 'IndexController@index') +Route::get('/{react}', [Base\IndexController::class, 'index']) ->where('react', '^(?!(\/)?(api|auth|admin|daemon)).+'); diff --git a/routes/server.php b/routes/server.php deleted file mode 100644 index fb8c124996..0000000000 --- a/routes/server.php +++ /dev/null @@ -1,10 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -Route::get('/')->name('server.index'); -Route::get('/console')->name('server.console'); diff --git a/server.php b/server.php deleted file mode 100644 index de038652fe..0000000000 --- a/server.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ -$uri = urldecode( - parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) -); - -// This file allows us to emulate Apache's "mod_rewrite" functionality from the -// built-in PHP web server. This provides a convenient way to test a Laravel -// application without having installed a "real" web server software here. -if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) { - return false; -} - -require_once __DIR__ . '/public/index.php'; diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..97e485d15a --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +{ + composer ? null, + phpWithExtensions ? null, + pkgs ? import {}, +}: +with pkgs; + mkShell rec { + buildInputs = [ + alejandra + composer + nodejs_18 + nodePackages.yarn + phpWithExtensions + ]; + + shellHook = '' + PATH="$PATH:${pkgs.docker-compose}/libexec/docker/cli-plugins" + ''; + } diff --git a/storage/app/.gitignore b/storage/app/.gitignore index 104980aa3b..fedb287fec 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,6 +1,4 @@ * +!private/ +!public/ !.gitignore -!services/* -packs/**/* -services/.bak/* -!packs/.githold diff --git a/storage/app/packs/.githold b/storage/app/packs/.githold deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/tests/Browser/console/.gitignore b/storage/app/private/.gitignore similarity index 100% rename from tests/Browser/console/.gitignore rename to storage/app/private/.gitignore diff --git a/tests/Browser/screenshots/.gitignore b/storage/app/public/.gitignore similarity index 100% rename from tests/Browser/screenshots/.gitignore rename to storage/app/public/.gitignore diff --git a/storage/clockwork/.gitignore b/storage/clockwork/.gitignore new file mode 100755 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/storage/clockwork/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore deleted file mode 100755 index c96a04f008..0000000000 --- a/storage/debugbar/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore index 953edb7a99..05c4471f2b 100755 --- a/storage/framework/.gitignore +++ b/storage/framework/.gitignore @@ -1,7 +1,9 @@ -config.php -routes.php compiled.php -services.json +config.php +down events.scanned.php +maintenance.php +routes.php routes.scanned.php -down +schedule-* +services.json diff --git a/tailwind.config.js b/tailwind.config.js index b79975456c..7c814bd61b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,88 +1,36 @@ +const colors = require('tailwindcss/colors'); + +const gray = { + 50: 'hsl(216, 33%, 97%)', + 100: 'hsl(214, 15%, 91%)', + 200: 'hsl(210, 16%, 82%)', + 300: 'hsl(211, 13%, 65%)', + 400: 'hsl(211, 10%, 53%)', + 500: 'hsl(211, 12%, 43%)', + 600: 'hsl(209, 14%, 37%)', + 700: 'hsl(209, 18%, 30%)', + 800: 'hsl(209, 20%, 25%)', + 900: 'hsl(210, 24%, 16%)', +}; + module.exports = { + content: [ + './resources/scripts/**/*.{js,ts,tsx}', + ], theme: { - fontFamily: { - sans: [ 'Rubik', '-apple-system', 'BlinkMacSystemFont', '"Helvetica Neue"', '"Roboto"', 'system-ui', 'sans-serif' ], - header: [ '"IBM Plex Sans"', '"Roboto"', 'system-ui', 'sans-serif' ], - mono: [ '"IBM Plex Mono"', '"Source Code Pro"', 'SourceCodePro', 'Menlo', 'Monaco', 'Consolas', 'monospace' ], - }, - colors: { - transparent: 'transparent', - black: 'hsl(210, 27%, 10%)', - white: '#ffffff', - primary: { - 50: 'hsl(202, 100%, 95%)', // lightest - 100: 'hsl(204, 100%, 86%)', // lighter - 200: 'hsl(206, 93%, 73%)', - 300: 'hsl(208, 88%, 62%)', - 400: 'hsl(210, 83%, 53%)', // light - 500: 'hsl(212, 92%, 43%)', // base - 600: 'hsl(214, 95%, 36%)', // dark - 700: 'hsl(215, 96%, 32%)', - 800: 'hsl(216, 98%, 25%)', // darker - 900: 'hsl(218, 100%, 17%)', // darkest - }, - neutral: { - 50: 'hsl(216, 33%, 97%)', - 100: 'hsl(214, 15%, 91%)', - 200: 'hsl(210, 16%, 82%)', - 300: 'hsl(211, 13%, 65%)', - 400: 'hsl(211, 10%, 53%)', - 500: 'hsl(211, 12%, 43%)', - 600: 'hsl(209, 14%, 37%)', - 700: 'hsl(209, 18%, 30%)', - 800: 'hsl(209, 20%, 25%)', - 900: 'hsl(210, 24%, 16%)', - }, - red: { - 50: 'hsl(360, 100%, 95%)', - 100: 'hsl(360, 100%, 87%)', - 200: 'hsl(360, 100%, 80%)', - 300: 'hsl(360, 91%, 69%)', - 400: 'hsl(360, 83%, 62%)', - 500: 'hsl(356, 75%, 53%)', - 600: 'hsl(354, 85%, 44%)', - 700: 'hsl(352, 90%, 35%)', - 800: 'hsl(350, 94%, 28%)', - 900: 'hsl(348, 94%, 20%)', - }, - yellow: { - 50: 'hsl(49, 100%, 96%)', - 100: 'hsl(48, 100%, 88%)', - 200: 'hsl(48, 95%, 76%)', - 300: 'hsl(48, 94%, 68%)', - 400: 'hsl(44, 92%, 63%)', - 500: 'hsl(42, 87%, 55%)', - 600: 'hsl(36, 77%, 49%)', - 700: 'hsl(29, 80%, 44%)', - 800: 'hsl(22, 82%, 39%)', - 900: 'hsl(15, 86%, 30%)', - }, - cyan: { - 50: 'hsl(171, 82%, 94%)', - 100: 'hsl(172, 97%, 88%)', - 200: 'hsl(174, 96%, 78%)', - 300: 'hsl(176, 87%, 67%)', - 400: 'hsl(178, 78%, 57%)', - 500: 'hsl(180, 77%, 47%)', - 600: 'hsl(182, 85%, 39%)', - 700: 'hsl(184, 90%, 34%)', - 800: 'hsl(186, 91%, 29%)', - 900: 'hsl(188, 91%, 23%)', - }, - green: { - 50: 'hsl(125, 65%, 93%)', - 100: 'hsl(127, 65%, 85%)', - 200: 'hsl(124, 63%, 74%)', - 300: 'hsl(123, 53%, 55%)', - 400: 'hsl(123, 57%, 45%)', - 500: 'hsl(122, 73%, 35%)', - 600: 'hsl(122, 80%, 29%)', - 700: 'hsl(125, 79%, 26%)', - 800: 'hsl(125, 86%, 20%)', - 900: 'hsl(125, 97%, 14%)', - }, - }, extend: { + fontFamily: { + header: ['"IBM Plex Sans"', '"Roboto"', 'system-ui', 'sans-serif'], + }, + colors: { + black: '#131a20', + // "primary" and "neutral" are deprecated, prefer the use of "blue" and "gray" + // in new code. + primary: colors.blue, + gray: gray, + neutral: gray, + cyan: colors.cyan, + }, fontSize: { '2xs': '0.625rem', }, @@ -95,6 +43,9 @@ module.exports = { }, }, plugins: [ - require('@tailwindcss/forms'), + require('@tailwindcss/line-clamp'), + require('@tailwindcss/forms')({ + strategy: 'class', + }), ] }; diff --git a/tests/Assertions/AssertsActivityLogged.php b/tests/Assertions/AssertsActivityLogged.php new file mode 100644 index 0000000000..7bf990d5d3 --- /dev/null +++ b/tests/Assertions/AssertsActivityLogged.php @@ -0,0 +1,82 @@ +assertActivityLogged($event); + $this->assertActivityActor($event, $actor); + $this->assertActivitySubjects($event, ...$subjects); + } + + /** + * Asserts that the given activity log event was stored in the database. + */ + public function assertActivityLogged(string $event): void + { + Event::assertDispatched(ActivityLogged::class, fn ($e) => $e->is($event)); + } + + /** + * Asserts that a given activity log event was stored with the subjects being + * any of the values provided. + */ + public function assertActivitySubjects(string $event, Model|array $subjects): void + { + if (is_array($subjects)) { + \Webmozart\Assert\Assert::lessThanEq(count(func_get_args()), 2, 'Invalid call to ' . __METHOD__ . ': cannot provide additional arguments if providing an array.'); + } else { + $subjects = array_slice(func_get_args(), 1); + } + + Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $e) use ($event, $subjects) { + Assert::assertEquals($event, $e->model->event); + Assert::assertNotEmpty($e->model->subjects); + + foreach ($subjects as $subject) { + $match = $e->model->subjects->first(function (ActivityLogSubject $model) use ($subject) { + return $model->subject_type === $subject->getMorphClass() + && $model->subject_id = $subject->getKey(); + }); + + Assert::assertNotNull( + $match, + sprintf('Failed asserting that event "%s" includes a %s[%d] subject', $event, get_class($subject), $subject->getKey()) + ); + } + + return true; + }); + } + + /** + * Asserts that the provided event was logged into the activity logs with the provided + * actor model associated with it. + */ + public function assertActivityActor(string $event, ?Model $actor = null): void + { + Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $e) use ($event, $actor) { + Assert::assertEquals($event, $e->model->event); + + if (is_null($actor)) { + Assert::assertNull($e->actor()); + } else { + Assert::assertNotNull($e->actor()); + Assert::assertTrue($e->actor()->is($actor)); + } + + return true; + }); + } +} diff --git a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php index bd5ec0d1de..a74f541ea4 100644 --- a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php +++ b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php @@ -9,7 +9,7 @@ trait MiddlewareAttributeAssertionsTrait /** * Assert a request has an attribute assigned to it. */ - public function assertRequestHasAttribute(string $attribute) + public function assertRequestHasAttribute(string $attribute): void { Assert::assertTrue($this->request->attributes->has($attribute), 'Assert that request mock has ' . $attribute . ' attribute.'); } @@ -17,17 +17,15 @@ public function assertRequestHasAttribute(string $attribute) /** * Assert a request does not have an attribute assigned to it. */ - public function assertRequestMissingAttribute(string $attribute) + public function assertRequestMissingAttribute(string $attribute): void { Assert::assertFalse($this->request->attributes->has($attribute), 'Assert that request mock does not have ' . $attribute . ' attribute.'); } /** * Assert a request attribute matches an expected value. - * - * @param mixed $expected */ - public function assertRequestAttributeEquals($expected, string $attribute) + public function assertRequestAttributeEquals(mixed $expected, string $attribute): void { Assert::assertEquals($expected, $this->request->attributes->get($attribute)); } diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php deleted file mode 100644 index 68a6ddc1bb..0000000000 --- a/tests/Browser/BrowserTestCase.php +++ /dev/null @@ -1,122 +0,0 @@ -make(Kernel::class); - - $kernel->bootstrap(); - $kernel->call('migrate:fresh'); - } - - /** - * Setup tests. - */ - protected function setUp(): void - { - // Don't accidentally run the migrations aganist the non-testing database. Ask me - // how many times I've accidentally dropped my database... - if (env('DB_CONNECTION') !== 'testing') { - throw new BadMethodCallException('Cannot call browser tests using the non-testing database connection.'); - } - - parent::setUp(); - - // Gotta unset this to continue avoiding issues with the validation. - Model::unsetEventDispatcher(); - } - - /** - * Create the RemoteWebDriver instance. - * - * @return \Facebook\WebDriver\Remote\RemoteWebDriver - */ - protected function driver() - { - $options = (new ChromeOptions())->addArguments([ - '--disable-gpu', - '--disable-infobars', - ]); - - return RemoteWebDriver::create( - 'http://host.pterodactyl.local:4444/wd/hub', - DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, - $options - ) - ); - } - - /** - * Return an instance of the browser to be used for tests. - * - * @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - protected function newBrowser($driver): PterodactylBrowser - { - return new PterodactylBrowser($driver); - } - - /** - * Tear down the test and delete all cookies from the browser instance to address - * instances where the test would be kicked over to the login page. - */ - protected function tearDown(): void - { - /** @var \Pterodactyl\Tests\Browser\PterodactylBrowser $browser */ - foreach (static::$browsers as $browser) { - $browser->driver->manage()->deleteAllCookies(); - } - - parent::tearDown(); - } - - /** - * Return a user model to authenticate aganist and use in the tests. - */ - protected function user(array $attributes = []): User - { - return User::factory()->create(array_merge([ - 'password' => Hash::make(static::$userPassword), - ], $attributes)); - } -} diff --git a/tests/Browser/Pages/BasePage.php b/tests/Browser/Pages/BasePage.php deleted file mode 100644 index c2e451a3d7..0000000000 --- a/tests/Browser/Pages/BasePage.php +++ /dev/null @@ -1,19 +0,0 @@ - '.alert.success[role="alert"]', - '@@error' => '.alert.error[role="alert"]', - ]; - } -} diff --git a/tests/Browser/Pages/Dashboard/AccountPage.php b/tests/Browser/Pages/Dashboard/AccountPage.php deleted file mode 100644 index e1aef4c2a8..0000000000 --- a/tests/Browser/Pages/Dashboard/AccountPage.php +++ /dev/null @@ -1,41 +0,0 @@ - '#update-email-container #grid-email', - '@password' => '#update-email-container #grid-password[type="password"]', - '@submit' => '#update-email-container button[type="submit"]', - - '@current_password' => '#change-password-container #grid-password-current[type="password"]', - '@new_password' => '#change-password-container #grid-password-new[type="password"]', - '@confirm_password' => '#change-password-container #grid-password-new-confirm[type="password"]', - '@submit_password' => '#change-password-container button[type="submit"]', - - '@2fa_button' => '#grid-open-two-factor-modal', - '@2fa_modal' => '.modal-mask #configure-two-factor', - '@2fa_token' => '#configure-two-factor #container-enable-two-factor #grid-two-factor-token[type="number"]', - '@2fa_token_disable' => '#configure-two-factor #container-disable-two-factor #grid-two-factor-token-disable', - '@2fa_enable' => '#configure-two-factor #container-enable-two-factor button[type="submit"]', - '@2fa_disable' => '#configure-two-factor #container-disable-two-factor button.btn-red[type="submit"]', - '@2fa_cancel' => '#configure-two-factor #container-disable-two-factor button.btn-secondary', - ]); - } -} diff --git a/tests/Browser/Pages/LoginPage.php b/tests/Browser/Pages/LoginPage.php deleted file mode 100644 index ea60b0580f..0000000000 --- a/tests/Browser/Pages/LoginPage.php +++ /dev/null @@ -1,26 +0,0 @@ - '#grid-email', - '@username' => '#grid-username', - '@password' => '#grid-password', - '@loginButton' => '#grid-login-button', - '@submitButton' => 'button.btn.btn-jumbo[type="submit"]', - '@forgotPassword' => 'a[href="/auth/password"][aria-label="Forgot password"]', - '@goToLogin' => 'a[href="/auth/login"][aria-label="Go to login"]', - '@alertSuccess' => 'div[role="alert"].success > span.message', - '@alertDanger' => 'div[role="alert"].danger > span.message', - ]; - } -} diff --git a/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php b/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php deleted file mode 100644 index f6cef86c4e..0000000000 --- a/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php +++ /dev/null @@ -1,50 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->assertSee(trans('auth.forgot_password.label')) - ->click('@forgotPassword') - ->waitForLocation('/auth/password') - ->assertFocused('@email') - ->assertSeeIn('.input-open > p.text-xs', trans('auth.forgot_password.label_help')) - ->assertSeeIn('@submitButton', trans('auth.forgot_password.button')) - ->type('@email', 'unassociated@example.com') - ->assertSeeIn('@goToLogin', trans('auth.go_to_login')) - ->press('@submitButton') - ->waitForLocation('/auth/login') - ->assertSeeIn('div[role="alert"].success > span.message', 'We have e-mailed your password reset link!') - ->assertFocused('@username') - ->assertValue('@username', 'unassociated@example.com'); - }); - } - - /** - * Test that you can type in your email address and then click forgot password and have - * the email maintained on the new page. - */ - public function testEmailCarryover() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->type('@username', 'dane@example.com') - ->click('@forgotPassword') - ->waitForLocation('/auth/password') - ->assertFocused('@email') - ->assertValue('@email', 'dane@example.com'); - }); - } -} diff --git a/tests/Browser/Processes/Authentication/LoginProcessTest.php b/tests/Browser/Processes/Authentication/LoginProcessTest.php deleted file mode 100644 index 0806cd7896..0000000000 --- a/tests/Browser/Processes/Authentication/LoginProcessTest.php +++ /dev/null @@ -1,83 +0,0 @@ -user = $this->user(); - } - - /** - * Test that a user can login successfully using their email address. - */ - public function testLoginUsingEmail() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->email) - ->type('@password', self::$userPassword) - ->click('@loginButton') - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } - - /** - * Test that a user can login successfully using their username. - */ - public function testLoginUsingUsername() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->username) - ->type('@password', self::$userPassword) - ->click('@loginButton') - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } - - /** - * Test that entering the wrong password shows the expected error and then allows - * us to login without clearing the username field. - */ - public function testLoginWithErrors() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->logout() - ->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->email) - ->type('@password', 'invalid') - ->click('@loginButton') - ->waitFor('.alert.error') - ->assertSeeIn('.alert.error', trans('auth.failed')) - ->assertValue('@username', $this->user->email) - ->assertValue('@password', '') - ->assertFocused('@password') - ->type('@password', self::$userPassword) - ->keys('@password', [WebDriverKeys::ENTER]) - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php b/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php deleted file mode 100644 index 6efe4ee032..0000000000 --- a/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php +++ /dev/null @@ -1,67 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertValue('@email', $this->user->email) - ->type('@email', 'new.email@example.com') - ->type('@password', 'Password123') - ->click('@submit') - ->waitFor('@@success') - ->assertSeeIn('@@success', trans('dashboard/account.email.updated')) - ->assertValue('@email', 'new.email@example.com'); - - $this->assertDatabaseHas('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']); - }); - } - - /** - * Test that the validation error message shows up when an invalid email is entered. - */ - public function testInvalidEmailShowsErrors() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertMissing('@email ~ .input-help.error') - ->type('@email', 'admin') - ->assertVisible('@email ~ .input-help.error') - ->assertSeeIn('@email ~ .input-help.error', 'The email field must be a valid email.') - ->type('@email', 'admin@example.com') - ->assertMissing('@email ~ .input-help.error'); - }); - } - - /** - * Test that entering the wrong password for an account returns an error. - */ - public function testInvalidPasswordShowsError() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@email', 'new.email@example.com') - ->click('@submit') - ->assertFocused('@password') - ->type('@password', 'test1234') - ->click('@submit') - ->waitFor('@@error') - ->assertSeeIn('@@error', trans('validation.internal.invalid_password')) - ->assertValue('@email', 'new.email@example.com'); - - $this->assertDatabaseMissing('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php b/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php deleted file mode 100644 index 4b8a5a3b64..0000000000 --- a/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php +++ /dev/null @@ -1,55 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@current_password', self::$userPassword) - ->assertMissing('@new_password ~ .input-help.error') - ->type('@new_password', 'test') - ->assertSeeIn('@new_password ~ .input-help.error', 'The password field must be at least 8 characters.') - ->type('@new_password', 'Test1234') - ->assertMissing('@new_password ~ .input-help.error') - ->assertMissing('@confirm_password ~ .input-help.error') - ->type('@confirm_password', 'test') - ->assertSeeIn('@confirm_password ~ .input-help.error', 'The password value is not valid.') - ->type('@confirm_password', 'Test1234') - ->assertMissing('@confirm_password ~ .input-help.error') - ->click('@submit_password') - ->waitFor('@@success') - ->assertSeeIn('@@success', 'Your password has been updated.') - ->assertInputValue('@current_password', '') - ->assertInputValue('@new_password', '') - ->assertInputValue('@confirm_password', ''); - }); - } - - /** - * Test that invalid passwords result in the expected error message. - */ - public function testInvalidPassword() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@current_password', 'badpassword') - ->type('@new_password', 'testtest') - ->type('@confirm_password', 'testtest') - ->click('@submit_password') - ->waitFor('@@error') - ->assertSeeIn('@@error', trans('validation.internal.invalid_password')) - ->assertInputValue('@current_password', ''); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/DashboardTestCase.php b/tests/Browser/Processes/Dashboard/DashboardTestCase.php deleted file mode 100644 index efbeb13b4e..0000000000 --- a/tests/Browser/Processes/Dashboard/DashboardTestCase.php +++ /dev/null @@ -1,23 +0,0 @@ -user = $this->user(); - } -} diff --git a/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php b/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php deleted file mode 100644 index 463286ddb8..0000000000 --- a/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php +++ /dev/null @@ -1,111 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertMissing('.modal-mask') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->pause(500)// seems to fix fragile test - ->clickPosition(100, 100) - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->click('svg[role="button"][aria-label="Close modal"]') - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->keys('', [WebDriverKeys::ESCAPE]) - ->waitUntilMissing('@2fa_modal'); - }); - } - - /** - * Test that a user that does not have two-factor enabled can enable it on their account. - */ - public function testTwoFactorCanBeEnabled() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.setup.title')) - ->assertFocused('@2fa_token') - ->waitFor('#grid-qr-code') - ->assertSee(trans('dashboard/account.two_factor.setup.help')); - - // Grab information from the database so we can ensure the correct things are showing up. - // Also because we need to generate a code to send through and activate it with. - $updated = $this->user->fresh(); - - $secret = Crypt::decrypt($updated->totp_secret); - $code = (new Google2FA())->getCurrentOtp($secret); - - $browser->assertSeeIn('code', $secret) - ->assertVisible('@2fa_enable[disabled="disabled"]') - ->assertMissing('@2fa_token ~ .input-help.error') - ->type('@2fa_token', '12') - ->assertSeeIn('@2fa_token ~ .input-help.error', 'The token length must be 6.') - ->type('@2fa_token', $code) - ->assertMissing('@2fa_token ~ .input-help.error') - ->click('@2fa_enable') - ->waitUntilMissing('@2fa_modal') - ->assertSeeIn('@@success', trans('dashboard/account.two_factor.enabled')); - - $this->assertDatabaseHas('users', ['id' => $this->user->id, 'use_totp' => 1]); - }); - } - - /** - * Test that a user can disable two-factor authentication on thier account. - */ - public function testTwoFactorCanBeDisabled() - { - $secret = (new Google2FA())->generateSecretKey(16); - - $this->user->update([ - 'use_totp' => true, - 'totp_secret' => Crypt::encrypt($secret), - ]); - - $this->browse(function (PterodactylBrowser $browser) use ($secret) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.disable.title')) - ->click('@2fa_cancel') - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.disable.title')) - ->assertVisible('@2fa_disable[disabled="disabled"]') - ->assertVisible('@2fa_cancel') - ->assertFocused('@2fa_token_disable') - ->assertMissing('@2fa_token_disable ~ .input-help.error') - ->type('@2fa_token_disable', '12') - ->assertSeeIn('@2fa_token_disable ~ .input-help.error', 'The token length must be 6.'); - - $token = (new Google2FA())->getCurrentOtp($secret); - - $browser->type('@2fa_token_disable', $token) - ->assertMissing('@2fa_token_disable ~ .input-help.error') - ->click('@2fa_disable') - ->waitUntilMissing('@2fa_modal') - ->assertSeeIn('@@success', trans('dashboard/account.two_factor.disabled')); - }); - } -} diff --git a/tests/Browser/PterodactylBrowser.php b/tests/Browser/PterodactylBrowser.php deleted file mode 100644 index 9136a6a65a..0000000000 --- a/tests/Browser/PterodactylBrowser.php +++ /dev/null @@ -1,55 +0,0 @@ -driver->getMouse()->mouseMove(null, $x, $y)->click(); - - return $this; - } - - /** - * Perform a case insensitive search for a string in the body. - * - * @param string $text - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - public function assertSee($text) - { - return $this->assertSeeIn('', $text); - } - - /** - * Perform a case insensitive search for a string in a given selector. - * - * @param string $selector - * @param string $text - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - public function assertSeeIn($selector, $text) - { - $fullSelector = $this->resolver->format($selector); - $element = $this->resolver->findOrFail($selector); - - PHPUnit::assertTrue( - Str::contains(mb_strtolower($element->getText()), mb_strtolower($text)), - "Did not see expected text [{$text}] within element [{$fullSelector}] using case-insensitive search." - ); - - return $this; - } -} diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php deleted file mode 100644 index b7ff2256c8..0000000000 --- a/tests/CreatesApplication.php +++ /dev/null @@ -1,22 +0,0 @@ -make(Kernel::class)->bootstrap(); - - return $app; - } -} diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index daca172833..73a4f8f51a 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application; +use Illuminate\Http\Request; use Pterodactyl\Models\User; use PHPUnit\Framework\Assert; use Pterodactyl\Models\ApiKey; @@ -19,15 +20,9 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase use DatabaseTransactions; use IntegrationJsonRequestAssertions; - /** - * @var \Pterodactyl\Models\ApiKey - */ - private $key; + private ApiKey $key; - /** - * @var \Pterodactyl\Models\User - */ - private $user; + private User $user; /** * Bootstrap application API tests. Creates a default admin user and associated API key @@ -40,10 +35,9 @@ public function setUp(): void $this->user = $this->createApiUser(); $this->key = $this->createApiKey($this->user); - $this->withHeader('Accept', 'application/vnd.pterodactyl.v1+json'); - $this->withHeader('Authorization', 'Bearer ' . $this->getApiKey()->identifier . decrypt($this->getApiKey()->token)); - - $this->withMiddleware('api..key:' . ApiKey::TYPE_APPLICATION); + $this + ->withHeader('Accept', 'application/vnd.pterodactyl.v1+json') + ->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token)); } public function getApiUser(): User @@ -62,17 +56,10 @@ public function getApiKey(): ApiKey protected function createNewDefaultApiKey(User $user, array $permissions = []): ApiKey { $this->key = $this->createApiKey($user, $permissions); - $this->refreshHeaders($this->key); - return $this->key; - } + $this->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token)); - /** - * Refresh the authorization header for a request to use a different API key. - */ - protected function refreshHeaders(ApiKey $key) - { - $this->withHeader('Authorization', 'Bearer ' . $key->identifier . decrypt($key->token)); + return $this->key; } /** @@ -110,9 +97,12 @@ protected function createApiKey(User $user, array $permissions = []): ApiKey */ protected function getTransformer(string $abstract): BaseTransformer { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = $this->app->make($abstract); - $transformer->setKey($this->getApiKey()); + $request = Request::createFromGlobals(); + $request->setUserResolver(function () { + return $this->getApiKey()->user; + }); + + $transformer = $abstract::fromRequest($request); Assert::assertInstanceOf(BaseTransformer::class, $transformer); Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer); diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 939dd8622c..ed75413ac0 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -5,9 +5,9 @@ use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Location; -use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Transformers\Api\Application\NodeTransformer; use Pterodactyl\Transformers\Api\Application\ServerTransformer; +use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; class LocationControllerTest extends ApplicationApiIntegrationTestCase @@ -128,13 +128,13 @@ public function testUpdateLocation() $response = $this->patchJson('/api/application/locations/' . $location->id, [ 'short' => 'new inhouse', - 'long' => 'This is my new inhouse location' + 'long' => 'This is my new inhouse location', ]); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'] + 'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('locations', ['short' => 'new inhouse', 'long' => 'This is my new inhouse location']); @@ -161,7 +161,7 @@ public function testDeleteLocation() } /** - * Test that all of the defined relationships for a location can be loaded successfully. + * Test that all the defined relationships for a location can be loaded successfully. */ public function testRelationshipsCanBeLoaded() { @@ -265,16 +265,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/locations/' . $location->id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a location's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 0482e91eb1..07a527e0f0 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -3,34 +3,19 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Nests; use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Illuminate\Http\Response; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; class EggControllerTest extends ApplicationApiIntegrationTestCase { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->repository = $this->app->make(EggRepositoryInterface::class); - } - /** * Test that all the eggs belonging to a given nest can be returned. */ public function testListAllEggsInNest() { - $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); + $eggs = Egg::query()->where('nest_id', 1)->get(); $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); $response->assertStatus(Response::HTTP_OK); @@ -64,7 +49,7 @@ public function testListAllEggsInNest() $this->assertSame( $expected, $actual, - 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[{$expected}]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[{$actual}]." + 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[$expected]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[$actual]." ); } } @@ -74,7 +59,7 @@ public function testListAllEggsInNest() */ public function testReturnSingleEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); $response->assertStatus(Response::HTTP_OK); @@ -92,11 +77,11 @@ public function testReturnSingleEgg() } /** - * Test that a single egg and all of the defined relationships can be returned. + * Test that a single egg and all the defined relationships can be returned. */ public function testReturnSingleEggWithRelationships() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); $response->assertStatus(Response::HTTP_OK); @@ -117,7 +102,7 @@ public function testReturnSingleEggWithRelationships() */ public function testGetMissingEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); $this->assertNotFoundJson($response); @@ -129,23 +114,10 @@ public function testGetMissingEgg() */ public function testErrorReturnedIfNoPermission() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); $this->assertAccessDeniedJson($response); } - - /** - * Test that a nests's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $egg = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); - - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 58434ec4cc..799fc18ac7 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -9,10 +9,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; + private NestRepositoryInterface $repository; /** * Setup tests. @@ -25,7 +22,7 @@ public function setUp(): void } /** - * Test that the expected nests are returned in the request. + * Test that the expected nests are returned by the request. */ public function testNestResponse() { @@ -127,17 +124,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/nests/' . $nest->id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a nest's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Nodes/NodeController/UpdateNodeTest.php b/tests/Integration/Api/Application/Nodes/NodeController/UpdateNodeTest.php new file mode 100644 index 0000000000..3d6c01ebef --- /dev/null +++ b/tests/Integration/Api/Application/Nodes/NodeController/UpdateNodeTest.php @@ -0,0 +1,54 @@ +for(Location::factory())->create(); + $location = Location::factory()->create(); + + $this->mock(DaemonConfigurationRepository::class, function (MockInterface $mock) use ($node) { + $mock->expects('setNode')->with(\Mockery::on(fn ($value) => $value->is($node)))->andReturnSelf(); + $mock->expects('update')->withAnyArgs()->andReturn( + new Response() + ); + }); + + $this->patchJson(route('api.application.nodes.update', ['node' => $node]), [ + 'name' => 'New Name', + 'description' => 'New Description', + 'location_id' => $location->id, + 'fqdn' => 'new.example.com', + 'scheme' => 'https', + 'memory' => 100, + 'memory_overallocate' => 10, + 'disk' => 200, + 'disk_overallocate' => 20, + 'daemon_sftp' => 1101, + 'daemon_listen' => 1102, + ]) + ->assertOk() + ->assertJsonPath('object', 'node') + ->assertJsonPath('attributes.name', 'New Name') + ->assertJsonPath('attributes.description', 'New Description') + ->assertJsonPath('attributes.fqdn', 'new.example.com') + ->assertJsonPath('attributes.scheme', 'https') + ->assertJsonPath('attributes.memory', 100) + ->assertJsonPath('attributes.memory_overallocate', 10) + ->assertJsonPath('attributes.disk', 200) + ->assertJsonPath('attributes.disk_overallocate', 20) + ->assertJsonPath('attributes.daemon_sftp', 1101) + ->assertJsonPath('attributes.daemon_listen', 1102); + + $this->assertEquals($location->id, $node->refresh()->location_id); + } +} diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 983710b16d..fc37b72329 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Users; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; @@ -13,7 +14,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetRemoteUser() { - $user = User::factory()->create(); + $user = User::factory()->create(['external_id' => Str::random()]); $response = $this->getJson('/api/application/users/external/' . $user->external_id); $response->assertStatus(Response::HTTP_OK); @@ -60,22 +61,10 @@ public function testGetMissingUser() */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); + $user = User::factory()->create(['external_id' => Str::random()]); $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); $response = $this->getJson('/api/application/users/external/' . $user->external_id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a users's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index f084d2ed2c..7c70bb10bb 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -55,7 +55,7 @@ public function testGetUsers() 'first_name' => $this->getApiUser()->name_first, 'last_name' => $this->getApiUser()->name_last, 'language' => $this->getApiUser()->language, - 'root_admin' => (bool) $this->getApiUser()->root_admin, + 'root_admin' => $this->getApiUser()->root_admin, '2fa' => (bool) $this->getApiUser()->totp_enabled, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), @@ -201,18 +201,6 @@ public function testErrorReturnedIfNoPermission() $this->assertAccessDeniedJson($response); } - /** - * Test that a users's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/nil'); - $this->assertAccessDeniedJson($response); - } - /** * Test that a user can be created. */ @@ -243,6 +231,8 @@ public function testCreateUser() 'resource' => route('api.application.users.view', $user->id), ], ], true); + + $this->assertActivityFor('user:user.create', $this->getApiUser(), $user); } /** @@ -291,9 +281,8 @@ public function testDeleteUser() /** * Test that an API key without write permissions cannot create, update, or * delete a user model. - * - * @dataProvider userWriteEndpointsDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('userWriteEndpointsDataProvider')] public function testApiKeyWithoutWritePermissions(string $method, string $url) { $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => AdminAcl::READ]); @@ -311,7 +300,7 @@ public function testApiKeyWithoutWritePermissions(string $method, string $url) * Endpoints that should return a 403 error when the key does not have write * permissions for user management. */ - public function userWriteEndpointsDataProvider(): array + public static function userWriteEndpointsDataProvider(): array { return [ ['postJson', '/api/application/users'], diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 971b834210..b941c92a94 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -2,9 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Client; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Illuminate\Http\Response; +use Pterodactyl\Models\Subuser; +use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Hash; +use Pterodactyl\Jobs\RevokeSftpAccessJob; class AccountControllerTest extends ClientApiIntegrationTestCase { @@ -13,7 +17,7 @@ class AccountControllerTest extends ClientApiIntegrationTestCase */ public function testAccountDetailsAreReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->get('/api/client/account'); @@ -37,26 +41,48 @@ public function testAccountDetailsAreReturned() */ public function testEmailIsUpdated() { - /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - $response = $this->actingAs($user)->putJson('/api/client/account/email', [ - 'email' => 'hodor@example.com', - 'password' => 'password', - ]); + $this->actingAs($user) + ->putJson('/api/client/account/email', [ + 'email' => $email = Str::random() . '@example.com', + 'password' => 'password', + ]) + ->assertNoContent(); - $response->assertStatus(Response::HTTP_NO_CONTENT); + $this->assertActivityFor('user:account.email-changed', $user, $user); + $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => $email]); + } - $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => 'hodor@example.com']); + public function testEmailChangeIsThrottled(): void + { + $users = User::factory()->count(2)->create(); + $endpoint = route('api:client.account.update-email'); + + for ($i = 0; $i < 3; ++$i) { + $this->actingAs($users[0]) + ->putJson($endpoint, ['email' => "foo+{$i}@example.com", 'password' => 'password']) + ->assertNoContent(); + } + + $this + ->putJson($endpoint, ['email' => 'bar@example.com', 'password' => 'password']) + ->assertTooManyRequests(); + + // The other user should still be able to update their email because the throttle + // is tied to the account, not to the IP address. + $this->actingAs($users[1]) + ->putJson($endpoint, ['email' => 'bar+1@example.com', 'password' => 'password']) + ->assertNoContent(); } /** - * Tests that an email is not updated if the password provided in the reuqest is not + * Tests that an email is not updated if the password provided in the request is not * valid for the account. */ public function testEmailIsNotUpdatedWhenPasswordIsInvalid() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->putJson('/api/client/account/email', [ @@ -75,7 +101,7 @@ public function testEmailIsNotUpdatedWhenPasswordIsInvalid() */ public function testEmailIsNotUpdatedWhenNotValid() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->putJson('/api/client/account/email', [ @@ -95,6 +121,35 @@ public function testEmailIsNotUpdatedWhenNotValid() $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); $response->assertJsonPath('errors.0.meta.rule', 'email'); $response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.'); + + + /* + * RFCs limit certain parts of an email to certain character limits. + * A limit of <= 64 for the local, then <= 63 for each domain label. + */ + $local = str_repeat(Str::random(10), 6) . '1234'; + $label = str_repeat(Str::random(10), 6) . '1'; + + + $response = $this->actingAs($user)->putJson('/api/client/account/email', [ + 'email' => "1$local@$label.$label", // exceed RFC limit for local part + 'password' => 'password', + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.'); + $response->assertJsonPath('errors.0.meta.source_field', 'email'); + + + $response = $this->actingAs($user)->putJson('/api/client/account/email', [ + 'email' => "$local@1234$label.$label", // exceed RFC limit for label part + 'password' => 'password', + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.'); + $response->assertJsonPath('errors.0.meta.source_field', 'email'); + } /** @@ -102,16 +157,26 @@ public function testEmailIsNotUpdatedWhenNotValid() */ public function testPasswordIsUpdated() { - /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); + // Assign the user to two servers, one as the owner the other as a subuser, both + // on different nodes to ensure our logic fires off correctly and the user has their + // credentials revoked on both nodes. + $server = $this->createServerModel(['owner_id' => $user->id]); + $server2 = $this->createServerModel(); + Subuser::factory()->for($server2)->for($user)->create(); + $initialHash = $user->password; - $response = $this->actingAs($user)->putJson('/api/client/account/password', [ - 'current_password' => 'password', - 'password' => 'New_Password1', - 'password_confirmation' => 'New_Password1', - ]); + Bus::fake([RevokeSftpAccessJob::class]); + + $this->actingAs($user) + ->putJson('/api/client/account/password', [ + 'current_password' => 'password', + 'password' => 'New_Password1', + 'password_confirmation' => 'New_Password1', + ]) + ->assertNoContent(); $user = $user->refresh(); @@ -119,7 +184,12 @@ public function testPasswordIsUpdated() $this->assertTrue(Hash::check('New_Password1', $user->password)); $this->assertFalse(Hash::check('password', $user->password)); - $response->assertStatus(Response::HTTP_NO_CONTENT); + $this->assertActivityFor('user:account.password-changed', $user, $user); + $this->assertNotEquals($server->node_id, $server2->node_id); + + Bus::assertDispatchedTimes(RevokeSftpAccessJob::class, 2); + Bus::assertDispatched(fn(RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server->node)); + Bus::assertDispatched(fn(RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server2->node)); } /** @@ -128,7 +198,7 @@ public function testPasswordIsUpdated() */ public function testPasswordIsNotUpdatedIfCurrentPasswordIsInvalid() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->putJson('/api/client/account/password', [ @@ -171,7 +241,7 @@ public function testErrorIsReturnedForInvalidRequestData() */ public function testErrorIsReturnedIfPasswordIsNotConfirmed() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->putJson('/api/client/account/password', [ diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 77bb8e1a68..6017a72b29 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -5,6 +5,8 @@ use Pterodactyl\Models\User; use Illuminate\Http\Response; use Pterodactyl\Models\ApiKey; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\ActivityLogged; class ApiKeyControllerTest extends ClientApiIntegrationTestCase { @@ -23,32 +25,19 @@ protected function tearDown(): void */ public function testApiKeysAreReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, + /** @var ApiKey $key */ + $key = ApiKey::factory()->for($user)->create([ 'key_type' => ApiKey::TYPE_ACCOUNT, ]); - $response = $this->actingAs($user)->get('/api/client/account/api-keys'); - - $response->assertOk(); - $response->assertJson([ - 'object' => 'list', - 'data' => [ - [ - 'object' => 'api_key', - 'attributes' => [ - 'identifier' => $key->identifier, - 'description' => $key->memo, - 'allowed_ips' => $key->allowed_ips, - 'last_used_at' => null, - 'created_at' => $key->created_at->toIso8601String(), - ], - ], - ], - ]); + $response = $this->actingAs($user)->get('/api/client/account/api-keys') + ->assertOk() + ->assertJsonPath('object', 'list') + ->assertJsonPath('data.0.object', ApiKey::RESOURCE_NAME); + + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $key); } /** @@ -56,12 +45,13 @@ public function testApiKeysAreReturned() * API key secret is returned as metadata in the response since it will not be returned * after that point. */ - public function testApiKeyCanBeCreatedForAccount() + #[\PHPUnit\Framework\Attributes\DataProvider('validIPAddressDataProvider')] + public function testApiKeyCanBeCreatedForAccount(array $data) { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - // Small sub-test to ensure we're always comparing the number of keys to the + // Small subtest to ensure we're always comparing the number of keys to the // specific logged in account, and not just the total number of keys stored in // the database. ApiKey::factory()->times(10)->create([ @@ -71,52 +61,61 @@ public function testApiKeyCanBeCreatedForAccount() $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => 'Test Description', - 'allowed_ips' => ['127.0.0.1'], - ]); - - $response->assertOk(); + 'allowed_ips' => $data, + ]) + ->assertOk() + ->assertJsonPath('object', ApiKey::RESOURCE_NAME); - /** @var \Pterodactyl\Models\ApiKey $key */ + /** @var ApiKey $key */ $key = ApiKey::query()->where('identifier', $response->json('attributes.identifier'))->firstOrFail(); - $response->assertJson([ - 'object' => 'api_key', - 'attributes' => [ - 'identifier' => $key->identifier, - 'description' => 'Test Description', - 'allowed_ips' => ['127.0.0.1'], - 'last_used_at' => null, - 'created_at' => $key->created_at->toIso8601String(), - ], - 'meta' => [ - 'secret_token' => decrypt($key->token), - ], - ]); + $this->assertJsonTransformedWith($response->json('attributes'), $key); + $response->assertJsonPath('meta.secret_token', decrypt($key->token)); + + $this->assertActivityFor('user:api-key.create', $user, [$key, $user]); + } + + /** + * Block requests to create an API key specifying more than 50 IP addresses. + */ + public function testApiKeyCannotSpecifyMoreThanFiftyIps() + { + $ips = []; + for ($i = 0; $i < 100; ++$i) { + $ips[] = '127.0.0.' . $i; + } + + $this->actingAs(User::factory()->create()) + ->postJson('/api/client/account/api-keys', [ + 'description' => 'Test Data', + 'allowed_ips' => $ips, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The allowed ips may not have more than 50 items.'); } /** - * Test that no more than 5 API keys can exist at any one time for an account. This prevents + * Test that no more than 25 API keys can exist at any one time for an account. This prevents * a DoS attack vector against the panel. * * @see https://github.com/pterodactyl/panel/security/advisories/GHSA-pjmh-7xfm-r4x9 + * @see https://github.com/pterodactyl/panel/issues/4394 */ - public function testNoMoreThanFiveApiKeysCanBeCreatedForAnAccount() + public function testApiKeyLimitIsApplied() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - ApiKey::factory()->times(5)->create([ - 'user_id' => $user->id, + ApiKey::factory()->times(25)->for($user)->create([ 'key_type' => ApiKey::TYPE_ACCOUNT, ]); - $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ + $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => 'Test Description', 'allowed_ips' => ['127.0.0.1'], - ]); - - $response->assertStatus(Response::HTTP_BAD_REQUEST); - $response->assertJsonPath('errors.0.code', 'DisplayException'); - $response->assertJsonPath('errors.0.detail', 'You have reached the account limit for number of API keys.'); + ]) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.code', 'DisplayException') + ->assertJsonPath('errors.0.detail', 'You have reached the account limit for number of API keys.'); } /** @@ -126,26 +125,33 @@ public function testNoMoreThanFiveApiKeysCanBeCreatedForAnAccount() */ public function testValidationErrorIsReturnedForBadRequests() { - /** @var \Pterodactyl\Models\User $user */ - $user = User::factory()->create(); + $this->actingAs(User::factory()->create()); - $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ + $this->postJson('/api/client/account/api-keys', [ 'description' => '', 'allowed_ips' => ['127.0.0.1'], - ]); - - $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); - $response->assertJsonPath('errors.0.meta.rule', 'required'); - $response->assertJsonPath('errors.0.detail', 'The description field is required.'); + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.rule', 'required') + ->assertJsonPath('errors.0.detail', 'The description field is required.'); - $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ + $this->postJson('/api/client/account/api-keys', [ 'description' => str_repeat('a', 501), 'allowed_ips' => ['127.0.0.1'], - ]); - - $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); - $response->assertJsonPath('errors.0.meta.rule', 'max'); - $response->assertJsonPath('errors.0.detail', 'The description may not be greater than 500 characters.'); + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.rule', 'max') + ->assertJsonPath('errors.0.detail', 'The description may not be greater than 500 characters.'); + + $this->postJson('/api/client/account/api-keys', [ + 'description' => 'Foobar', + 'allowed_ips' => ['hodor', '127.0.0.1', 'hodor/24'], + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', '"hodor" is not a valid IP address or CIDR range.') + ->assertJsonPath('errors.0.meta.source_field', 'allowed_ips.0') + ->assertJsonPath('errors.1.detail', '"hodor/24" is not a valid IP address or CIDR range.') + ->assertJsonPath('errors.1.meta.source_field', 'allowed_ips.2'); } /** @@ -153,11 +159,10 @@ public function testValidationErrorIsReturnedForBadRequests() */ public function testApiKeyCanBeDeleted() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, + /** @var ApiKey $key */ + $key = ApiKey::factory()->for($user)->create([ 'key_type' => ApiKey::TYPE_ACCOUNT, ]); @@ -165,6 +170,7 @@ public function testApiKeyCanBeDeleted() $response->assertStatus(Response::HTTP_NO_CONTENT); $this->assertDatabaseMissing('api_keys', ['id' => $key->id]); + $this->assertActivityFor('user:api-key.delete', $user, $user); } /** @@ -172,9 +178,9 @@ public function testApiKeyCanBeDeleted() */ public function testNonExistentApiKeyDeletionReturns404Error() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ + /** @var ApiKey $key */ $key = ApiKey::factory()->create([ 'user_id' => $user->id, 'key_type' => ApiKey::TYPE_ACCOUNT, @@ -184,6 +190,7 @@ public function testNonExistentApiKeyDeletionReturns404Error() $response->assertNotFound(); $this->assertDatabaseHas('api_keys', ['id' => $key->id]); + Event::assertNotDispatched(ActivityLogged::class); } /** @@ -192,39 +199,56 @@ public function testNonExistentApiKeyDeletionReturns404Error() */ public function testApiKeyBelongingToAnotherUserCannotBeDeleted() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\User $user2 */ + /** @var User $user2 */ $user2 = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user2->id, + /** @var ApiKey $key */ + $key = ApiKey::factory()->for($user2)->create([ 'key_type' => ApiKey::TYPE_ACCOUNT, ]); - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier); - $response->assertNotFound(); + $this->actingAs($user) + ->deleteJson('/api/client/account/api-keys/' . $key->identifier) + ->assertNotFound(); $this->assertDatabaseHas('api_keys', ['id' => $key->id]); + Event::assertNotDispatched(ActivityLogged::class); } /** - * Tests that an application API key also belonging to the logged in user cannot be + * Tests that an application API key also belonging to the logged-in user cannot be * deleted through this endpoint if it exists. */ public function testApplicationApiKeyCannotBeDeleted() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, + /** @var ApiKey $key */ + $key = ApiKey::factory()->for($user)->create([ 'key_type' => ApiKey::TYPE_APPLICATION, ]); - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier); - $response->assertNotFound(); + $this->actingAs($user) + ->deleteJson('/api/client/account/api-keys/' . $key->identifier) + ->assertNotFound(); $this->assertDatabaseHas('api_keys', ['id' => $key->id]); } + + /** + * Provides some different IP address combinations that can be used when + * testing that we accept the expected IP values. + */ + public static function validIPAddressDataProvider(): array + { + return [ + [[]], + [['127.0.0.1']], + [['127.0.0.1', '::1']], + [['::ffff:7f00:1']], + [['127.0.0.1', '192.168.1.100', '192.168.10.10/28']], + [['127.0.0.1/32', '192.168.100.100/27', '::1', '::1/128']], + ]; + } } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 80b397ca82..d7ceb34a22 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -2,14 +2,12 @@ namespace Pterodactyl\Tests\Integration\Api\Client; -use ReflectionClass; use Pterodactyl\Models\Node; use Pterodactyl\Models\Task; use Pterodactyl\Models\User; -use InvalidArgumentException; +use Pterodactyl\Models\Model; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Database; use Pterodactyl\Models\Location; use Pterodactyl\Models\Schedule; @@ -18,6 +16,7 @@ use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; +use Illuminate\Database\Eloquent\Model as EloquentModel; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase @@ -44,82 +43,50 @@ protected function tearDown(): void * to keep re-assigning variables. * * @param \Illuminate\Http\Response $response + * @param \Illuminate\Http\Request $request * * @return \Illuminate\Testing\TestResponse */ - protected function createTestResponse($response) + protected function createTestResponse($response, $request) { - return TestResponse::fromBaseResponse($response); + return TestResponse::fromBaseResponse($response, $request); } /** * Returns a link to the specific resource using the client API. - * - * @param mixed $model - * @param string|null $append */ - protected function link($model, $append = null): string + protected function link(mixed $model, ?string $append = null): string { - $link = ''; switch (get_class($model)) { case Server::class: - $link = "/api/client/servers/{$model->uuid}"; + $link = "/api/client/servers/$model->uuid"; break; case Schedule::class: - $link = "/api/client/servers/{$model->server->uuid}/schedules/{$model->id}"; + $link = "/api/client/servers/{$model->server->uuid}/schedules/$model->id"; break; case Task::class: - $link = "/api/client/servers/{$model->schedule->server->uuid}/schedules/{$model->schedule->id}/tasks/{$model->id}"; + $link = "/api/client/servers/{$model->schedule->server->uuid}/schedules/{$model->schedule->id}/tasks/$model->id"; break; case Allocation::class: - $link = "/api/client/servers/{$model->server->uuid}/network/allocations/{$model->id}"; + $link = "/api/client/servers/{$model->server->uuid}/network/allocations/$model->id"; break; case Backup::class: - $link = "/api/client/servers/{$model->server->uuid}/backups/{$model->uuid}"; + $link = "/api/client/servers/{$model->server->uuid}/backups/$model->uuid"; break; default: - throw new InvalidArgumentException(sprintf('Cannot create link for Model of type %s', class_basename($model))); + throw new \InvalidArgumentException(sprintf('Cannot create link for Model of type %s', class_basename($model))); } return $link . ($append ? '/' . ltrim($append, '/') : ''); } - /** - * Generates a user and a server for that user. If an array of permissions is passed it - * is assumed that the user is actually a subuser of the server. - * - * @param string[] $permissions - */ - protected function generateTestAccount(array $permissions = []): array - { - /** @var \Pterodactyl\Models\User $user */ - $user = User::factory()->create(); - - if (empty($permissions)) { - return [$user, $this->createServerModel(['user_id' => $user->id])]; - } - - /** @var \Pterodactyl\Models\Server $server */ - $server = $this->createServerModel(); - - Subuser::query()->create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'permissions' => $permissions, - ]); - - return [$user, $server]; - } - /** * Asserts that the data passed through matches the output of the data from the transformer. This * will remove the "relationships" key when performing the comparison. - * - * @param \Pterodactyl\Models\Model|\Illuminate\Database\Eloquent\Model $model */ - protected function assertJsonTransformedWith(array $data, $model) + protected function assertJsonTransformedWith(array $data, Model|EloquentModel $model) { - $reflect = new ReflectionClass($model); + $reflect = new \ReflectionClass($model); $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName()); $transformer = new $transformer(); diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index 3ab6b19128..44084450a6 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Tests\Integration\Api\Client; +use Ramsey\Uuid\Uuid; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; @@ -11,7 +12,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase { /** - * Test that only the servers a logged in user is assigned to are returned by the + * Test that only the servers a logged-in user is assigned to are returned by the * API endpoint. Obviously there are cases such as being an administrator or being * a subuser, but for this test we just want to test a basic scenario and pretend * subusers do not exist at all. @@ -53,8 +54,8 @@ public function testServersAreFilteredUsingNameAndUuidInformation() $servers = [ $this->createServerModel(['user_id' => $users[0]->id, 'name' => 'Julia']), $this->createServerModel(['user_id' => $users[1]->id, 'uuidShort' => '12121212', 'name' => 'Janice']), - $this->createServerModel(['user_id' => $users[1]->id, 'uuid' => '88788878-12356789', 'external_id' => 'ext123', 'name' => 'Julia']), - $this->createServerModel(['user_id' => $users[1]->id, 'uuid' => '88788878-abcdefgh', 'name' => 'Jennifer']), + $this->createServerModel(['user_id' => $users[1]->id, 'uuid' => Uuid::uuid4()->toString(), 'external_id' => 'ext123', 'name' => 'Julia']), + $this->createServerModel(['user_id' => $users[1]->id, 'uuid' => Uuid::uuid4()->toString(), 'name' => 'Jennifer']), ]; $this->actingAs($users[1])->getJson('/api/client?filter[*]=Julia') @@ -77,16 +78,14 @@ public function testServersAreFilteredUsingNameAndUuidInformation() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.attributes.identifier', $servers[1]->uuidShort); - $this->actingAs($users[1])->getJson('/api/client?filter[*]=88788878') + $this->actingAs($users[1])->getJson("/api/client?filter[*]={$servers[2]->uuidShort}") ->assertOk() - ->assertJsonCount(2, 'data') - ->assertJsonPath('data.0.attributes.identifier', $servers[2]->uuidShort) - ->assertJsonPath('data.1.attributes.identifier', $servers[3]->uuidShort); + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.attributes.identifier', $servers[2]->uuidShort); $this->actingAs($users[1])->getJson('/api/client?filter[*]=88788878-abcd') ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.attributes.identifier', $servers[3]->uuidShort); + ->assertJsonCount(0, 'data'); $this->actingAs($users[0])->getJson('/api/client?filter[*]=Julia&type=admin-all') ->assertOk() @@ -101,8 +100,8 @@ public function testServersAreFilteredUsingNameAndUuidInformation() */ public function testServersAreFilteredUsingAllocationInformation() { - /** @var \Pterodactyl\Models\User $user */ - /** @var \Pterodactyl\Models\Server $server */ + /** @var User $user */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount(); $server2 = $this->createServerModel(['user_id' => $user->id, 'node_id' => $server->node_id]); @@ -203,7 +202,7 @@ public function testFilterOnlyOwnerServers() */ public function testPermissionsAreReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $this->actingAs($user) @@ -241,7 +240,7 @@ public function testOnlyAdminLevelServersAreReturned() ]); // Only servers 2 & 3 (0 indexed) should be returned by the API at this point. The user making - // the request is the owner of server 0, and a subuser of server 1 so they should be exluded. + // the request is the owner of server 0, and a subuser of server 1, so they should be excluded. $response = $this->actingAs($users[0])->getJson('/api/client?type=admin'); $response->assertOk(); @@ -285,11 +284,9 @@ public function testAllServersAreReturnedToAdmin() /** * Test that no servers get returned if the user requests all admin level servers by using * ?type=admin or ?type=admin-all in the request. - * - * @param string $type - * @dataProvider filterTypeDataProvider */ - public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser($type) + #[\PHPUnit\Framework\Attributes\DataProvider('filterTypeDataProvider')] + public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser(string $type) { /** @var \Pterodactyl\Models\User[] $users */ $users = User::factory()->times(3)->create(); @@ -310,7 +307,7 @@ public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser($type */ public function testOnlyPrimaryAllocationIsReturnedToSubuser() { - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $server->allocation->notes = 'Test notes'; $server->allocation->save(); @@ -332,10 +329,7 @@ public function testOnlyPrimaryAllocationIsReturnedToSubuser() $response->assertJsonPath('data.0.attributes.relationships.allocations.data.0.attributes.notes', null); } - /** - * @return array - */ - public function filterTypeDataProvider() + public static function filterTypeDataProvider(): array { return [['admin'], ['admin-all']]; } diff --git a/tests/Integration/Api/Client/SSHKeyControllerTest.php b/tests/Integration/Api/Client/SSHKeyControllerTest.php new file mode 100644 index 0000000000..03d40f709a --- /dev/null +++ b/tests/Integration/Api/Client/SSHKeyControllerTest.php @@ -0,0 +1,144 @@ +create(); + $user2 = User::factory()->create(); + + $key = UserSSHKey::factory()->for($user)->create(); + UserSSHKey::factory()->for($user2)->rsa()->create(); + + $this->actingAs($user); + $response = $this->getJson('/api/client/account/ssh-keys') + ->assertOk() + ->assertJsonPath('object', 'list') + ->assertJsonPath('data.0.object', UserSSHKey::RESOURCE_NAME); + + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $key); + } + + /** + * Test that a user's SSH key can be deleted, and that passing the fingerprint + * of another user's SSH key won't delete that key. + */ + public function testSSHKeyCanBeDeleted() + { + $user = User::factory()->create(); + $user2 = User::factory()->create(); + + $key = UserSSHKey::factory()->for($user)->create(); + $key2 = UserSSHKey::factory()->for($user2)->create(); + + $endpoint = '/api/client/account/ssh-keys/remove'; + + $this->actingAs($user); + $this->postJson($endpoint) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta', ['source_field' => 'fingerprint', 'rule' => 'required']); + + $this->postJson($endpoint, ['fingerprint' => $key->fingerprint])->assertNoContent(); + + $this->assertSoftDeleted($key); + $this->assertNotSoftDeleted($key2); + + $this->postJson($endpoint, ['fingerprint' => $key->fingerprint])->assertNoContent(); + $this->postJson($endpoint, ['fingerprint' => $key2->fingerprint])->assertNoContent(); + + $this->assertNotSoftDeleted($key2); + } + + public function testDSAKeyIsRejected() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->dsa()->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'DSA keys are not supported.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + } + + public function testWeakRSAKeyIsRejected() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->rsa(true)->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'RSA keys must be at least 2048 bytes in length.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + } + + public function testInvalidOrPrivateKeyIsRejected() + { + $user = User::factory()->create(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => 'invalid', + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided is not valid.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + + $key = EC::createKey('Ed25519'); + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->toString('PKCS8'), + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided is not valid.'); + } + + public function testPublicKeyCanBeStored() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertOk() + ->assertJsonPath('object', UserSSHKey::RESOURCE_NAME) + ->assertJsonPath('attributes.public_key', $key->public_key); + + $this->assertCount(1, $user->sshKeys); + $this->assertEquals($key->public_key, $user->sshKeys[0]->public_key); + } + + public function testPublicKeyThatAlreadyExistsCannotBeAddedASecondTime() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->for($user)->create(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided already exists on your account.'); + + $this->assertEquals(1, $user->sshKeys()->count()); + } +} diff --git a/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php b/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php index b698d8c7e0..c5d6ac127c 100644 --- a/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php @@ -8,9 +8,7 @@ class AllocationAuthorizationTest extends ClientApiIntegrationTestCase { - /** - * @dataProvider methodDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('methodDataProvider')] public function testAccessToAServersAllocationsIsRestrictedProperly(string $method, string $endpoint) { // The API $user is the owner of $server1. @@ -46,10 +44,7 @@ public function testAccessToAServersAllocationsIsRestrictedProperly(string $meth $this->actingAs($user)->json($method, $this->link($server3, '/network/allocations/' . $allocation3->id . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ - public function methodDataProvider(): array + public static function methodDataProvider(): array { return [ ['POST', ''], diff --git a/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php index 6b8da4048b..61d93a62aa 100644 --- a/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php @@ -23,9 +23,8 @@ public function setUp(): void /** * Tests that a new allocation can be properly assigned to a server. - * - * @dataProvider permissionDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('permissionDataProvider')] public function testNewAllocationCanBeAssignedToServer(array $permission) { /** @var \Pterodactyl\Models\Server $server */ @@ -72,7 +71,7 @@ public function testAllocationCannotBeCreatedIfNotEnabled() } /** - * Test that an allocation cannot be created if the server has reached it's allocation limit. + * Test that an allocation cannot be created if the server has reached its allocation limit. */ public function testAllocationCannotBeCreatedIfServerIsAtLimit() { @@ -86,10 +85,7 @@ public function testAllocationCannotBeCreatedIfServerIsAtLimit() ->assertJsonPath('errors.0.detail', 'Cannot assign additional allocations to this server: limit has been reached.'); } - /** - * @return array - */ - public function permissionDataProvider() + public static function permissionDataProvider(): array { return [[[Permission::ACTION_ALLOCATION_CREATE]], [[]]]; } diff --git a/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php index c986de784a..f6bd466e63 100644 --- a/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php @@ -12,15 +12,15 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase /** * Test that an allocation is deleted from the server and the notes are properly reset * to an empty value on assignment. - * - * @dataProvider permissionDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('permissionDataProvider')] public function testAllocationCanBeDeletedFromServer(array $permission) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permission); + $server->update(['allocation_limit' => 2]); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create([ 'server_id' => $server->id, 'node_id' => $server->node_id, @@ -40,7 +40,7 @@ public function testErrorIsReturnedIfUserDoesNotHavePermission() /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_CREATE]); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create([ 'server_id' => $server->id, 'node_id' => $server->node_id, @@ -60,6 +60,7 @@ public function testErrorIsReturnedIfAllocationIsPrimary() { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount(); + $server->update(['allocation_limit' => 2]); $this->actingAs($user)->deleteJson($this->link($server->allocation)) ->assertStatus(Response::HTTP_BAD_REQUEST) @@ -67,6 +68,22 @@ public function testErrorIsReturnedIfAllocationIsPrimary() ->assertJsonPath('errors.0.detail', 'You cannot delete the primary allocation for this server.'); } + public function testAllocationCannotBeDeletedIfServerLimitIsNotDefined() + { + [$user, $server] = $this->generateTestAccount(); + + /** @var Allocation $allocation */ + $allocation = Allocation::factory()->forServer($server)->create(['notes' => 'Test notes']); + + $this->actingAs($user)->deleteJson($this->link($allocation)) + ->assertStatus(400) + ->assertJsonPath('errors.0.detail', 'You cannot delete allocations for this server: no allocation limit is set.'); + + $allocation->refresh(); + $this->assertNotNull($allocation->notes); + $this->assertEquals($server->id, $allocation->server_id); + } + /** * Test that an allocation cannot be deleted if it does not belong to the server instance. */ @@ -80,10 +97,7 @@ public function testErrorIsReturnedIfAllocationDoesNotBelongToServer() $this->actingAs($user)->deleteJson($this->link($server, "/network/allocations/{$server2->allocation_id}"))->assertNotFound(); } - /** - * @return array - */ - public function permissionDataProvider() + public static function permissionDataProvider(): array { return [[[Permission::ACTION_ALLOCATION_DELETE]], [[]]]; } diff --git a/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php b/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php index aaaf38a6fa..33fde157c6 100644 --- a/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Backup; -use Mockery; use Carbon\CarbonImmutable; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Subuser; @@ -11,9 +10,7 @@ class BackupAuthorizationTest extends ClientApiIntegrationTestCase { - /** - * @dataProvider methodDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('methodDataProvider')] public function testAccessToAServersBackupIsRestrictedProperly(string $method, string $endpoint) { // The API $user is the owner of $server1. @@ -31,7 +28,7 @@ public function testAccessToAServersBackupIsRestrictedProperly(string $method, s $backup2 = Backup::factory()->create(['server_id' => $server2->id, 'completed_at' => CarbonImmutable::now()]); $backup3 = Backup::factory()->create(['server_id' => $server3->id, 'completed_at' => CarbonImmutable::now()]); - $this->instance(DeleteBackupService::class, $mock = Mockery::mock(DeleteBackupService::class)); + $this->instance(DeleteBackupService::class, $mock = \Mockery::mock(DeleteBackupService::class)); if ($method === 'DELETE') { $mock->expects('handle')->andReturnUndefined(); @@ -55,10 +52,7 @@ public function testAccessToAServersBackupIsRestrictedProperly(string $method, s $this->actingAs($user)->json($method, $this->link($server3, '/backups/' . $backup3->uuid . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ - public function methodDataProvider(): array + public static function methodDataProvider(): array { return [ ['GET', ''], diff --git a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php index 0fc80610da..d6aea997d6 100644 --- a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php +++ b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php @@ -2,17 +2,18 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Backup; -use Mockery; +use Mockery\MockInterface; use Illuminate\Http\Response; use Pterodactyl\Models\Backup; -use Pterodactyl\Models\AuditLog; use Pterodactyl\Models\Permission; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\ActivityLogged; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; class DeleteBackupTest extends ClientApiIntegrationTestCase { - private $repository; + private MockInterface $repository; public function setUp(): void { @@ -34,32 +35,30 @@ public function testUserWithoutPermissionCannotDeleteBackup() /** * Tests that a backup can be deleted for a server and that it is properly updated * in the database. Once deleted there should also be a corresponding record in the - * audit logs table for this API call. + * activity logs table for this API call. */ public function testBackupCanBeDeleted() { + Event::fake([ActivityLogged::class]); + [$user, $server] = $this->generateTestAccount([Permission::ACTION_BACKUP_DELETE]); - /** @var \Pterodactyl\Models\Backup $backup */ + /** @var Backup $backup */ $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with(Mockery::on(function ($value) use ($backup) { - return $value instanceof Backup && $value->uuid === $backup->uuid; - }))->andReturn(new Response()); + $this->repository->expects('setServer->delete')->with( + \Mockery::on(function ($value) use ($backup) { + return $value instanceof Backup && $value->uuid === $backup->uuid; + }) + )->andReturn(new Response()); $this->actingAs($user)->deleteJson($this->link($backup))->assertStatus(Response::HTTP_NO_CONTENT); $backup->refresh(); + $this->assertSoftDeleted($backup); - $this->assertNotNull($backup->deleted_at); + $this->assertActivityFor('server:backup.delete', $user, [$backup, $backup->server]); $this->actingAs($user)->deleteJson($this->link($backup))->assertStatus(Response::HTTP_NOT_FOUND); - - $event = $backup->audits()->where('action', AuditLog::SERVER__BACKUP_DELETED)->latest()->first(); - - $this->assertNotNull($event); - $this->assertFalse($event->is_system); - $this->assertEquals($backup->server_id, $event->server_id); - $this->assertEquals($user->id, $event->user_id); } } diff --git a/tests/Integration/Api/Client/Server/CommandControllerTest.php b/tests/Integration/Api/Client/Server/CommandControllerTest.php index d6010b675c..14d6da3f25 100644 --- a/tests/Integration/Api/Client/Server/CommandControllerTest.php +++ b/tests/Integration/Api/Client/Server/CommandControllerTest.php @@ -2,9 +2,9 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; -use Mockery; use GuzzleHttp\Psr7\Request; use Illuminate\Http\Response; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Permission; use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Psr7\Response as GuzzleResponse; @@ -14,20 +14,6 @@ class CommandControllerTest extends ClientApiIntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->repository = Mockery::mock(DaemonCommandRepository::class); - $this->app->instance(DaemonCommandRepository::class, $this->repository); - } - /** * Test that a validation error is returned if there is no command present in the * request. @@ -36,7 +22,7 @@ public function testValidationErrorIsReturnedIfNoCommandIsPresent() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => '', ]); @@ -52,7 +38,7 @@ public function testSubuserWithoutPermissionReceivesError() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); @@ -66,12 +52,14 @@ public function testCommandCanSendToServer() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); - $this->repository->expects('setServer')->with(Mockery::on(function ($value) use ($server) { - return $value->uuid === $server->uuid; - }))->andReturnSelf(); - $this->repository->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); + $mock = $this->mock(DaemonCommandRepository::class); + $mock->expects('setServer') + ->with(\Mockery::on(fn (Server $value) => $value->is($server))) + ->andReturnSelf(); + + $mock->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); @@ -86,13 +74,14 @@ public function testErrorIsReturnedWhenServerIsOffline() { [$user, $server] = $this->generateTestAccount(); - $this->repository->expects('setServer->send')->andThrows( + $mock = $this->mock(DaemonCommandRepository::class); + $mock->expects('setServer->send')->andThrows( new DaemonConnectionException( new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY)) ) ); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); diff --git a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php index d7063ea336..c31aed6e29 100644 --- a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Database; -use Mockery; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; @@ -13,9 +12,7 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase { - /** - * @dataProvider methodDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('methodDataProvider')] public function testAccessToAServersDatabasesIsRestrictedProperly(string $method, string $endpoint) { // The API $user is the owner of $server1. @@ -35,14 +32,10 @@ public function testAccessToAServersDatabasesIsRestrictedProperly(string $method $database2 = Database::factory()->create(['server_id' => $server2->id, 'database_host_id' => $host->id]); $database3 = Database::factory()->create(['server_id' => $server3->id, 'database_host_id' => $host->id]); - $this->instance(DatabasePasswordService::class, $mock = Mockery::mock(DatabasePasswordService::class)); - $this->instance(DatabaseManagementService::class, $mock2 = Mockery::mock(DatabaseManagementService::class)); - - if ($method === 'POST') { - $mock->expects('handle')->andReturnUndefined(); - } else { - $mock2->expects('delete')->andReturnUndefined(); - } + $this + ->mock($method === 'POST' ? DatabasePasswordService::class : DatabaseManagementService::class) + ->expects($method === 'POST' ? 'handle' : 'delete') + ->andReturn($method === 'POST' ? 'foo' : null); $hashids = $this->app->make(HashidsInterface::class); // This is the only valid call for this test, accessing the database for the same @@ -63,10 +56,7 @@ public function testAccessToAServersDatabasesIsRestrictedProperly(string $method $this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ - public function methodDataProvider(): array + public static function methodDataProvider(): array { return [ ['POST', '/rotate-password'], diff --git a/tests/Integration/Api/Client/Server/Files/CompressFilesTest.php b/tests/Integration/Api/Client/Server/Files/CompressFilesTest.php new file mode 100644 index 0000000000..ccddc37739 --- /dev/null +++ b/tests/Integration/Api/Client/Server/Files/CompressFilesTest.php @@ -0,0 +1,45 @@ +generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); + + $this->postJson($this->link($server, '/files/compress'))->assertUnauthorized(); + + $this->actingAs($user) + ->postJson($this->link($server, '/files/compress')) + ->assertForbidden(); + } + + public function testEndpointTriggersWingsCall(): void + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_ARCHIVE]); + + $this->mock(DaemonFileRepository::class, function (MockInterface $mock) { + $mock->expects('setServer->compressFiles')->with('/', ['test.txt'])->andReturn([ + 'name' => 'test.tar.gz', + 'mime' => 'application/gzip', + ]); + }); + + $this->actingAs($user) + ->postJson($endpoint = $this->link($server, '/files/compress'), []) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta', ['source_field' => 'files', 'rule' => 'required']); + + $this->postJson($endpoint, ['root' => '/', 'files' => ['test.txt']]) + ->assertOk() + ->assertJsonPath('object', 'file_object') + ->assertJsonPath('attributes.name', 'test.tar.gz') + ->assertJsonPath('attributes.mimetype', 'application/gzip'); + } +} diff --git a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php index 9741af9740..0fc9a865b3 100644 --- a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php +++ b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php @@ -48,9 +48,8 @@ public function testServerAllocationsAreNotReturnedWithoutPermission() /** * Tests that notes on an allocation can be set correctly. - * - * @dataProvider updatePermissionsDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('updatePermissionsDataProvider')] public function testAllocationNotesCanBeUpdated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -96,9 +95,7 @@ public function testAllocationNotesCannotBeUpdatedByInvalidUsers() $this->actingAs($user)->postJson($this->link($server->allocation))->assertForbidden(); } - /** - * @dataProvider updatePermissionsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('updatePermissionsDataProvider')] public function testPrimaryAllocationCanBeModified(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -133,13 +130,8 @@ public function testPrimaryAllocationCannotBeModifiedByInvalidUser() ->assertForbidden(); } - public function updatePermissionsDataProvider() + public static function updatePermissionsDataProvider(): array { return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]]; } - - public function deletePermissionsDataProvider() - { - return [[[]], [[Permission::ACTION_ALLOCATION_DELETE]]]; - } } diff --git a/tests/Integration/Api/Client/Server/PowerControllerTest.php b/tests/Integration/Api/Client/Server/PowerControllerTest.php index 6aed481a85..54daa0e604 100644 --- a/tests/Integration/Api/Client/Server/PowerControllerTest.php +++ b/tests/Integration/Api/Client/Server/PowerControllerTest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; -use Mockery; use Illuminate\Http\Response; use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; @@ -16,14 +15,14 @@ class PowerControllerTest extends ClientApiIntegrationTestCase * the command to the server. * * @param string[] $permissions - * @dataProvider invalidPermissionDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('invalidPermissionDataProvider')] public function testSubuserWithoutPermissionsReceivesError(string $action, array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action]) + ->postJson("/api/client/servers/$server->uuid/power", ['signal' => $action]) ->assertStatus(Response::HTTP_FORBIDDEN); } @@ -34,7 +33,7 @@ public function testInvalidPowerSignalResultsInError() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/power", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/power", [ 'signal' => 'invalid', ]); @@ -45,18 +44,17 @@ public function testInvalidPowerSignalResultsInError() /** * Test that sending a valid power actions works. - * - * @dataProvider validPowerActionDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('validPowerActionDataProvider')] public function testActionCanBeSentToServer(string $action, string $permission) { - $service = Mockery::mock(DaemonPowerRepository::class); + $service = \Mockery::mock(DaemonPowerRepository::class); $this->app->instance(DaemonPowerRepository::class, $service); [$user, $server] = $this->generateTestAccount([$permission]); $service->expects('setServer') - ->with(Mockery::on(function ($value) use ($server) { + ->with(\Mockery::on(function ($value) use ($server) { return $server->uuid === $value->uuid; })) ->andReturnSelf() @@ -65,14 +63,14 @@ public function testActionCanBeSentToServer(string $action, string $permission) ->with(trim($action)); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action]) + ->postJson("/api/client/servers/$server->uuid/power", ['signal' => $action]) ->assertStatus(Response::HTTP_NO_CONTENT); } /** * Returns invalid permission combinations for a given power action. */ - public function invalidPermissionDataProvider(): array + public static function invalidPermissionDataProvider(): array { return [ ['start', [Permission::ACTION_CONTROL_STOP, Permission::ACTION_CONTROL_RESTART]], @@ -83,7 +81,7 @@ public function invalidPermissionDataProvider(): array ]; } - public function validPowerActionDataProvider(): array + public static function validPowerActionDataProvider(): array { return [ ['start', Permission::ACTION_CONTROL_START], diff --git a/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php b/tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php similarity index 81% rename from tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php rename to tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php index 7c713dd338..82c1a442fe 100644 --- a/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php +++ b/tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php @@ -2,28 +2,27 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; -use Mockery; use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; -class ResourceUtilitizationControllerTest extends ClientApiIntegrationTestCase +class ResourceUtilizationControllerTest extends ClientApiIntegrationTestCase { /** * Test that the resource utilization for a server is returned in the expected format. */ public function testServerResourceUtilizationIsReturned() { - $service = Mockery::mock(DaemonServerRepository::class); + $service = \Mockery::mock(DaemonServerRepository::class); $this->app->instance(DaemonServerRepository::class, $service); [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - $service->expects('setServer')->with(Mockery::on(function ($value) use ($server) { + $service->expects('setServer')->with(\Mockery::on(function ($value) use ($server) { return $server->uuid === $value->uuid; }))->andReturnSelf()->getMock()->expects('getDetails')->andReturns([]); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/resources"); + $response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/resources"); $response->assertOk(); $response->assertJson([ diff --git a/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php index cde99b0902..26ab866e05 100644 --- a/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php @@ -11,15 +11,13 @@ class CreateServerScheduleTest extends ClientApiIntegrationTestCase { /** * Test that a schedule can be created for the server. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeCreatedForServer($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testScheduleCanBeCreatedForServer(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/schedules", [ 'name' => 'Test Schedule', 'is_active' => false, 'minute' => '0', @@ -33,7 +31,7 @@ public function testScheduleCanBeCreatedForServer($permissions) $this->assertNotNull($id = $response->json('attributes.id')); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::query()->findOrFail($id); $this->assertFalse($schedule->is_active); $this->assertFalse($schedule->is_processing); @@ -55,17 +53,17 @@ public function testScheduleValidationRules() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", []); + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/schedules", []); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); foreach (['name', 'minute', 'hour', 'day_of_month', 'day_of_week'] as $i => $field) { - $response->assertJsonPath("errors.{$i}.code", 'ValidationException'); - $response->assertJsonPath("errors.{$i}.meta.rule", 'required'); - $response->assertJsonPath("errors.{$i}.meta.source_field", $field); + $response->assertJsonPath("errors.$i.code", 'ValidationException'); + $response->assertJsonPath("errors.$i.meta.rule", 'required'); + $response->assertJsonPath("errors.$i.meta.source_field", $field); } $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules", [ + ->postJson("/api/client/servers/$server->uuid/schedules", [ 'name' => 'Testing', 'is_active' => 'no', 'minute' => '*', @@ -86,11 +84,11 @@ public function testSubuserCannotCreateScheduleWithoutPermissions() [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_UPDATE]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules", []) + ->postJson("/api/client/servers/$server->uuid/schedules", []) ->assertForbidden(); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SCHEDULE_CREATE]]]; } diff --git a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php index 25f2741159..c204ff4f54 100644 --- a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php @@ -12,11 +12,9 @@ class DeleteServerScheduleTest extends ClientApiIntegrationTestCase { /** * Test that a schedule can be deleted from the system. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeDeleted($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testScheduleCanBeDeleted(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -24,7 +22,7 @@ public function testScheduleCanBeDeleted($permissions) $task = Task::factory()->create(['schedule_id' => $schedule->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_NO_CONTENT); $this->assertDatabaseMissing('schedules', ['id' => $schedule->id]); @@ -39,7 +37,7 @@ public function testNotFoundErrorIsReturnedIfScheduleDoesNotExistAtAll() [$user, $server] = $this->generateTestAccount(); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/123456789") + ->deleteJson("/api/client/servers/$server->uuid/schedules/123456789") ->assertStatus(Response::HTTP_NOT_FOUND); } @@ -50,12 +48,12 @@ public function testNotFoundErrorIsReturnedIfScheduleDoesNotExistAtAll() public function testNotFoundErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_NOT_FOUND); $this->assertDatabaseHas('schedules', ['id' => $schedule->id]); @@ -72,13 +70,13 @@ public function testErrorIsReturnedIfSubuserDoesNotHaveRequiredPermissions() $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_FORBIDDEN); $this->assertDatabaseHas('schedules', ['id' => $schedule->id]); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SCHEDULE_DELETE]]]; } diff --git a/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php index 9964691aae..eafcf3c05a 100644 --- a/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php @@ -14,16 +14,15 @@ class ExecuteScheduleTest extends ClientApiIntegrationTestCase { /** * Test that a schedule can be executed and is updated in the database correctly. - * - * @dataProvider permissionsDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] public function testScheduleIsExecutedRightAway(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); Bus::fake(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create([ 'server_id' => $server->id, ]); @@ -33,7 +32,7 @@ public function testScheduleIsExecutedRightAway(array $permissions) $response->assertJsonPath('errors.0.code', 'DisplayException'); $response->assertJsonPath('errors.0.detail', 'Cannot process schedule for task execution: no tasks are registered.'); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create([ 'schedule_id' => $schedule->id, 'sequence_id' => 1, @@ -58,13 +57,13 @@ public function testUserWithoutScheduleUpdatePermissionCannotExecute() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_CREATE]); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertForbidden(); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php index 57c282ee76..cc5db4139c 100644 --- a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php @@ -22,25 +22,22 @@ protected function tearDown(): void /** * Test that schedules for a server are returned. - * - * @param array $permissions - * @param bool $individual - * @dataProvider permissionsDataProvider */ - public function testServerSchedulesAreReturned($permissions, $individual) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testServerSchedulesAreReturned(array $permissions, bool $individual) { [$user, $server] = $this->generateTestAccount($permissions); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 1, 'time_offset' => 0]); $response = $this->actingAs($user) ->getJson( $individual - ? "/api/client/servers/{$server->uuid}/schedules/{$schedule->id}" - : "/api/client/servers/{$server->uuid}/schedules" + ? "/api/client/servers/$server->uuid/schedules/$schedule->id" + : "/api/client/servers/$server->uuid/schedules" ) ->assertOk(); @@ -64,12 +61,12 @@ public function testServerSchedulesAreReturned($permissions, $individual) public function testScheduleBelongingToAnotherServerCannotBeViewed() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->getJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertNotFound(); } @@ -81,17 +78,17 @@ public function testUserWithoutPermissionCannotViewSchedules() [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules") + ->getJson("/api/client/servers/$server->uuid/schedules") ->assertForbidden(); $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->getJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertForbidden(); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [ [[], false], diff --git a/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php b/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php index 223e9a6737..26a83de4c2 100644 --- a/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php @@ -16,9 +16,8 @@ class ScheduleAuthorizationTest extends ClientApiIntegrationTestCase * * The comments within the test code itself are better at explaining exactly what is * being tested and protected against. - * - * @dataProvider methodDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('methodDataProvider')] public function testAccessToAServersSchedulesIsRestrictedProperly(string $method, string $endpoint) { // The API $user is the owner of $server1. @@ -54,10 +53,7 @@ public function testAccessToAServersSchedulesIsRestrictedProperly(string $method $this->actingAs($user)->json($method, $this->link($server3, '/schedules/' . $schedule3->id . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ - public function methodDataProvider(): array + public static function methodDataProvider(): array { return [ ['GET', ''], diff --git a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php index e99486d4ef..6b6ad10917 100644 --- a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php @@ -11,10 +11,8 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase { /** * The data to use when updating a schedule. - * - * @var array */ - private $updateData = [ + private array $updateData = [ 'name' => 'Updated Schedule Name', 'minute' => '5', 'hour' => '*', @@ -26,15 +24,13 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase /** * Test that a schedule can be updated. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeUpdated($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testScheduleCanBeUpdated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $expected = Utilities::getScheduleNextRunDate('5', '*', '*', '*', '*'); @@ -48,7 +44,7 @@ public function testScheduleCanBeUpdated($permissions) $this->assertFalse($schedule->is_active); $this->assertJsonTransformedWith($response->json('attributes'), $schedule); - $this->assertSame($expected->toIso8601String(), $schedule->next_run_at->toIso8601String()); + $this->assertSame($expected->toAtomString(), $schedule->next_run_at->toAtomString()); } /** @@ -58,7 +54,7 @@ public function testScheduleCanBeUpdated($permissions) public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); @@ -92,7 +88,7 @@ public function testScheduleIsProcessingIsSetToFalseWhenActiveStateChanges() { [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create([ 'server_id' => $server->id, 'is_active' => true, @@ -112,7 +108,7 @@ public function testScheduleIsProcessingIsSetToFalseWhenActiveStateChanges() $this->assertFalse($schedule->is_processing); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php index b031b4fb2b..c2d047a2b3 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php @@ -12,15 +12,13 @@ class CreateServerScheduleTaskTest extends ClientApiIntegrationTestCase { /** * Test that a task can be created. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testTaskCanBeCreated($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testTaskCanBeCreated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->assertEmpty($schedule->tasks); @@ -32,7 +30,7 @@ public function testTaskCanBeCreated($permissions) ]); $response->assertOk(); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::query()->findOrFail($response->json('attributes.id')); $this->assertSame($schedule->id, $task->schedule_id); @@ -50,14 +48,14 @@ public function testValidationErrorsAreReturned() { [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $response = $this->actingAs($user)->postJson($this->link($schedule, '/tasks'))->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); foreach (['action', 'payload', 'time_offset'] as $i => $field) { - $response->assertJsonPath("errors.{$i}.meta.rule", $field === 'payload' ? 'required_unless' : 'required'); - $response->assertJsonPath("errors.{$i}.meta.source_field", $field); + $response->assertJsonPath("errors.$i.meta.rule", $field === 'payload' ? 'required_unless' : 'required'); + $response->assertJsonPath("errors.$i.meta.source_field", $field); } $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [ @@ -95,7 +93,7 @@ public function testBackupsCanNotBeTaskedIfLimit0() { [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [ @@ -124,7 +122,7 @@ public function testErrorIsReturnedIfTooManyTasksExistForSchedule() [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); Task::factory()->times(2)->create(['schedule_id' => $schedule->id]); @@ -145,13 +143,13 @@ public function testErrorIsReturnedIfTooManyTasksExistForSchedule() public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}/tasks") + ->postJson("/api/client/servers/$server->uuid/schedules/$schedule->id/tasks") ->assertNotFound(); } @@ -163,7 +161,7 @@ public function testErrorIsReturnedIfSubuserDoesNotHaveScheduleUpdatePermissions { [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_CREATE]); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user) @@ -171,7 +169,7 @@ public function testErrorIsReturnedIfSubuserDoesNotHaveScheduleUpdatePermissions ->assertForbidden(); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php index dc9b3fd42f..b610dd3796 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php @@ -27,7 +27,7 @@ public function testScheduleNotBelongingToServerReturnsError() /** * Test that an error is returned if the task and schedule in the URL do not line up - * with eachother. + * with each other. */ public function testTaskBelongingToDifferentScheduleReturnsError() { @@ -37,7 +37,7 @@ public function testTaskBelongingToDifferentScheduleReturnsError() $schedule2 = Schedule::factory()->create(['server_id' => $server->id]); $task = Task::factory()->create(['schedule_id' => $schedule->id]); - $this->actingAs($user)->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule2->id}/tasks/{$task->id}")->assertNotFound(); + $this->actingAs($user)->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule2->id/tasks/$task->id")->assertNotFound(); } /** diff --git a/tests/Integration/Api/Client/Server/SettingsControllerTest.php b/tests/Integration/Api/Client/Server/SettingsControllerTest.php index edd4dc5430..1497927cd1 100644 --- a/tests/Integration/Api/Client/Server/SettingsControllerTest.php +++ b/tests/Integration/Api/Client/Server/SettingsControllerTest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; -use Mockery; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Permission; @@ -13,18 +12,18 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase { /** * Test that the server's name can be changed. - * - * @param array $permissions - * @dataProvider renamePermissionsDataProvider */ - public function testServerNameCanBeChanged($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('renamePermissionsDataProvider')] + public function testServerNameCanBeChanged(array $permissions) { - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $originalName = $server->name; + $originalDescription = $server->description; - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => '', + 'description' => '', ]); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -32,15 +31,18 @@ public function testServerNameCanBeChanged($permissions) $server = $server->refresh(); $this->assertSame($originalName, $server->name); + $this->assertSame($originalDescription, $server->description); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + ->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => 'Test Server Name', + 'description' => 'This is a test server.', ]) ->assertStatus(Response::HTTP_NO_CONTENT); $server = $server->refresh(); $this->assertSame('Test Server Name', $server->name); + $this->assertSame('This is a test server.', $server->description); } /** @@ -53,7 +55,7 @@ public function testSubuserCannotChangeServerNameWithoutPermission() $originalName = $server->name; $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + ->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => 'Test Server Name', ]) ->assertStatus(Response::HTTP_FORBIDDEN); @@ -65,21 +67,19 @@ public function testSubuserCannotChangeServerNameWithoutPermission() /** * Test that a server can be reinstalled. Honestly this test doesn't do much of anything other * than make sure the endpoint works since. - * - * @param array $permissions - * @dataProvider reinstallPermissionsDataProvider */ - public function testServerCanBeReinstalled($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('reinstallPermissionsDataProvider')] + public function testServerCanBeReinstalled(array $permissions) { - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $this->assertTrue($server->isInstalled()); - $service = Mockery::mock(DaemonServerRepository::class); + $service = \Mockery::mock(DaemonServerRepository::class); $this->app->instance(DaemonServerRepository::class, $service); $service->expects('setServer') - ->with(Mockery::on(function ($value) use ($server) { + ->with(\Mockery::on(function ($value) use ($server) { return $value->uuid === $server->uuid; })) ->andReturnSelf() @@ -87,7 +87,7 @@ public function testServerCanBeReinstalled($permissions) ->expects('reinstall') ->andReturnUndefined(); - $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/reinstall") + $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/reinstall") ->assertStatus(Response::HTTP_ACCEPTED); $server = $server->refresh(); @@ -103,19 +103,19 @@ public function testSubuserCannotReinstallServerWithoutPermission() [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/reinstall") + ->postJson("/api/client/servers/$server->uuid/settings/reinstall") ->assertStatus(Response::HTTP_FORBIDDEN); $server = $server->refresh(); $this->assertTrue($server->isInstalled()); } - public function renamePermissionsDataProvider(): array + public static function renamePermissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SETTINGS_RENAME]]]; } - public function reinstallPermissionsDataProvider(): array + public static function reinstallPermissionsDataProvider(): array { return [[[]], [[Permission::ACTION_SETTINGS_REINSTALL]]]; } diff --git a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php index 09a6abd029..493cc6a0a6 100644 --- a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php +++ b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php @@ -12,17 +12,15 @@ class GetStartupAndVariablesTest extends ClientApiIntegrationTestCase /** * Test that the startup command and variables are returned for a server, but only the variables * that can be viewed by a user (e.g. user_viewable=true). - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testStartupVariablesAreReturnedForServer($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testStartupVariablesAreReturnedForServer(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $egg = $this->cloneEggAndVariables($server->egg); - // BUNGEE_VERSION should never be returned back to the user in this API call, either in + // BUNGEE_VERSION should never be returned to the user in this API call, either in // the array of variables, or revealed in the startup command. $egg->variables()->first()->update([ 'user_viewable' => false, @@ -59,10 +57,7 @@ public function testStartupDataIsNotReturnedWithoutPermission() $this->actingAs($user2)->getJson($this->link($server) . '/startup')->assertNotFound(); } - /** - * @return array[] - */ - public function permissionsDataProvider() + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_STARTUP_READ]]]; } diff --git a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php index 0e5e421c1f..cb67cb0952 100644 --- a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php +++ b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php @@ -12,11 +12,9 @@ class UpdateStartupVariableTest extends ClientApiIntegrationTestCase { /** * Test that a startup variable can be edited successfully for a server. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testStartupVariableCanBeUpdated($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testStartupVariableCanBeUpdated(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); @@ -48,9 +46,8 @@ public function testStartupVariableCanBeUpdated($permissions) /** * Test that variables that are either not user_viewable, or not user_editable, cannot be * updated via this endpoint. - * - * @dataProvider permissionsDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] public function testStartupVariableCannotBeUpdatedIfNotUserViewableOrEditable(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ @@ -150,10 +147,7 @@ public function testStartupVariableCannotBeUpdatedIfNotUserViewable() $this->actingAs($user2)->putJson($this->link($server) . '/startup/variable')->assertNotFound(); } - /** - * @return \array[][] - */ - public function permissionsDataProvider() + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_STARTUP_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php index 94c9b17ef0..547f8b8640 100644 --- a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php @@ -16,11 +16,9 @@ class CreateServerSubuserTest extends ClientApiIntegrationTestCase /** * Test that a subuser can be created for a server. - * - * @param array $permissions - * @dataProvider permissionsDataProvider */ - public function testSubuserCanBeCreated($permissions) + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] + public function testSubuserCanBeCreated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -33,7 +31,7 @@ public function testSubuserCanBeCreated($permissions) $response->assertOk(); - /** @var \Pterodactyl\Models\User $subuser */ + /** @var User $subuser */ $subuser = User::query()->where('email', $email)->firstOrFail(); $response->assertJsonPath('object', Subuser::RESOURCE_NAME); @@ -49,6 +47,34 @@ public function testSubuserCanBeCreated($permissions) $this->assertJsonTransformedWith($expected, $subuser); } + /** + * Test that a newly created user account correctly causes the creation of a user:user.create + * activity log entry. + */ + public function testCreatingSubuserWithNewEmailLogsUserCreation() + { + [$user, $server] = $this->generateTestAccount(); + + $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ + 'email' => $email = $this->faker->email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertOk(); + + /** @var User $subuser */ + $subuser = User::query()->where('email', $email)->firstOrFail(); + + $this->assertActivityLogged('user:user.create'); + $this->assertDatabaseHas('activity_logs', [ + 'event' => 'user:user.create', + 'actor_type' => $user->getMorphClass(), + 'actor_id' => $user->id, + ]); + } + /** * Tests that an error is returned if a subuser attempts to create a new subuser and assign * permissions that their account does not also possess. @@ -62,7 +88,7 @@ public function testErrorIsReturnedIfAssigningPermissionsNotAssignedToSelf() ]); $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ - 'email' => $email = $this->faker->email, + 'email' => $this->faker->email, 'permissions' => [ Permission::ACTION_USER_CREATE, Permission::ACTION_USER_UPDATE, // This permission is not assigned to the subuser. @@ -81,7 +107,21 @@ public function testSubuserWithExcessivelyLongEmailCannotBeCreated() { [$user, $server] = $this->generateTestAccount(); - $email = str_repeat(Str::random(20), 9) . '1@gmail.com'; // 191 is the hard limit for the column in MySQL. + /* + * RFCs limit certain parts of an email to certain character limits. + * + * A limit of <= 64 for the local, then <= 63 for each domain label. + * We will stay below the limit to make sure we're within the 191 column limit for emails. + */ + $local = str_repeat(Str::random(10), 6) . '1234'; + $label = str_repeat(Str::random(10), 6) . '1'; + + // Make sure we're within the column limit + $email = "$local@$label.$label.au"; + + $this->assertSame(64, strlen($local)); + $this->assertSame(61, strlen($label)); + $this->assertSame(191, strlen($email)); $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ 'email' => $email, @@ -92,8 +132,13 @@ public function testSubuserWithExcessivelyLongEmailCannotBeCreated() $response->assertOk(); + // Exceed column limit of 1 >= and <= 191 + $email = "$local@$label.$label.com"; + + $this->assertSame(192, strlen($email)); + $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ - 'email' => $email . '.au', + 'email' => $email, 'permissions' => [ Permission::ACTION_USER_CREATE, ], @@ -112,7 +157,7 @@ public function testCreatingSubuserWithSameEmailAsExistingUserWorks() { [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\User $existing */ + /** @var User $existing */ $existing = User::factory()->create(['email' => $this->faker->email]); $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ @@ -156,7 +201,7 @@ public function testAddingSubuserThatAlreadyIsAssignedReturnsError() $response->assertJsonPath('errors.0.detail', 'A user with that email address is already assigned as a subuser for this server.'); } - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_USER_CREATE]]]; } diff --git a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php index eeb2485103..c9b7f8ed77 100644 --- a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php @@ -2,12 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Subuser; -use Mockery; use Ramsey\Uuid\Uuid; use Pterodactyl\Models\User; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Permission; -use Pterodactyl\Repositories\Wings\DaemonServerRepository; +use Illuminate\Support\Facades\Bus; +use Pterodactyl\Jobs\RevokeSftpAccessJob; +use PHPUnit\Framework\Attributes\TestWith; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; class DeleteSubuserTest extends ClientApiIntegrationTestCase @@ -23,19 +24,22 @@ class DeleteSubuserTest extends ClientApiIntegrationTestCase * * @see https://github.com/pterodactyl/panel/issues/2359 */ - public function testCorrectSubuserIsDeletedFromServer() + #[TestWith([null])] + #[TestWith(['18180000'])] + public function testCorrectSubuserIsDeletedFromServer(?string $prefix) { - $this->swap(DaemonServerRepository::class, $mock = Mockery::mock(DaemonServerRepository::class)); + Bus::fake([RevokeSftpAccessJob::class]); [$user, $server] = $this->generateTestAccount(); - /** @var \Pterodactyl\Models\User $differentUser */ + /** @var User $differentUser */ $differentUser = User::factory()->create(); + $real = Uuid::uuid4()->toString(); // Generate a UUID that lines up with a user in the database if it were to be cast to an int. - $uuid = $differentUser->id . str_repeat('a', strlen((string) $differentUser->id)) . substr(Uuid::uuid4()->toString(), 8); + $uuid = ($prefix ?: $differentUser->id) . substr($real, strlen($prefix ?: (string) $differentUser->id)); - /** @var \Pterodactyl\Models\User $subuser */ + /** @var User $subuser */ $subuser = User::factory()->create(['uuid' => $uuid]); Subuser::query()->forceCreate([ @@ -44,24 +48,12 @@ public function testCorrectSubuserIsDeletedFromServer() 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT], ]); - $mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined(); + $this->withoutExceptionHandling() + ->actingAs($user) + ->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent(); - $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); - - // Try the same test, but this time with a UUID that if cast to an int (shouldn't) line up with - // anything in the database. - $uuid = '18180000' . substr(Uuid::uuid4()->toString(), 8); - /** @var \Pterodactyl\Models\User $subuser */ - $subuser = User::factory()->create(['uuid' => $uuid]); - - Subuser::query()->forceCreate([ - 'user_id' => $subuser->id, - 'server_id' => $server->id, - 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT], - ]); - - $mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined(); - - $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); + Bus::assertDispatchedTimes(function (RevokeSftpAccessJob $job) use ($subuser, $server) { + return $job->user === $subuser->uuid && $job->target->is($server); + }); } } diff --git a/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php b/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php index f95e06f72b..18d47f2958 100644 --- a/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php @@ -2,23 +2,24 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Subuser; -use Mockery; use Pterodactyl\Models\User; use Pterodactyl\Models\Subuser; -use Pterodactyl\Repositories\Wings\DaemonServerRepository; +use Illuminate\Support\Facades\Bus; +use Pterodactyl\Jobs\RevokeSftpAccessJob; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; class SubuserAuthorizationTest extends ClientApiIntegrationTestCase { /** * Test that mismatched subusers are not accessible to a server. - * - * @dataProvider methodDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('methodDataProvider')] public function testUserCannotAccessResourceBelongingToOtherServers(string $method) { + Bus::fake([RevokeSftpAccessJob::class]); + // Generic subuser, the specific resource we're trying to access. - /** @var \Pterodactyl\Models\User $internal */ + /** @var User $internal */ $internal = User::factory()->create(); // The API $user is the owner of $server1. @@ -36,11 +37,6 @@ public function testUserCannotAccessResourceBelongingToOtherServers(string $meth Subuser::factory()->create(['server_id' => $server2->id, 'user_id' => $internal->id]); Subuser::factory()->create(['server_id' => $server3->id, 'user_id' => $internal->id]); - $this->instance(DaemonServerRepository::class, $mock = Mockery::mock(DaemonServerRepository::class)); - if ($method === 'DELETE') { - $mock->expects('setServer->revokeUserJTI')->with($internal->id)->andReturnUndefined(); - } - // This route is acceptable since they're accessing a subuser on their own server. $this->actingAs($user)->json($method, $this->link($server1, '/users/' . $internal->uuid))->assertStatus($method === 'POST' ? 422 : ($method === 'DELETE' ? 204 : 200)); @@ -48,12 +44,17 @@ public function testUserCannotAccessResourceBelongingToOtherServers(string $meth // errors out with a 403 since $user does not have the right permissions for this. $this->actingAs($user)->json($method, $this->link($server2, '/users/' . $internal->uuid))->assertForbidden(); $this->actingAs($user)->json($method, $this->link($server3, '/users/' . $internal->uuid))->assertNotFound(); + + if ($method === 'DELETE') { + Bus::assertDispatchedTimes(function (RevokeSftpAccessJob $job) use ($server1, $internal) { + return $job->user === $internal->uuid && $job->target->is($server1); + }); + } else { + Bus::assertNotDispatched(RevokeSftpAccessJob::class); + } } - /** - * @return \string[][] - */ - public function methodDataProvider(): array + public static function methodDataProvider(): array { return [['GET'], ['POST'], ['DELETE']]; } diff --git a/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php new file mode 100644 index 0000000000..661022c85a --- /dev/null +++ b/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php @@ -0,0 +1,151 @@ +generateTestAccount(['user.read']); + + $subuser = Subuser::factory() + ->for(User::factory()->create()) + ->for($server) + ->create([ + 'permissions' => ['control.start'], + ]); + + $this->postJson( + $endpoint = "/api/client/servers/$server->uuid/users/{$subuser->user->uuid}", + $data = [ + 'permissions' => [ + 'control.start', + 'control.stop', + ], + ] + ) + ->assertUnauthorized(); + + $this->actingAs($subuser->user)->postJson($endpoint, $data)->assertForbidden(); + $this->actingAs($user)->postJson($endpoint, $data)->assertForbidden(); + + $server->subusers()->where('user_id', $user->id)->update([ + 'permissions' => [ + Permission::ACTION_USER_UPDATE, + Permission::ACTION_CONTROL_START, + Permission::ACTION_CONTROL_STOP, + ], + ]); + + $this->postJson($endpoint, $data)->assertOk(); + + Bus::assertDispatchedTimes(function (RevokeSftpAccessJob $job) use ($server, $subuser) { + return $job->user === $subuser->user->uuid && $job->target->is($server); + }); + } + + /** + * Tests that permissions for the account are updated and any extraneous values + * we don't know about are removed. + */ + public function testPermissionsAreSavedToAccount() + { + Bus::fake([RevokeSftpAccessJob::class]); + + [$user, $server] = $this->generateTestAccount(); + + /** @var Subuser $subuser */ + $subuser = Subuser::factory() + ->for(User::factory()->create()) + ->for($server) + ->create([ + 'permissions' => ['control.restart', 'websocket.connect', 'foo.bar'], + ]); + + $this->actingAs($user) + ->postJson("/api/client/servers/$server->uuid/users/{$subuser->user->uuid}", [ + 'permissions' => [ + 'control.start', + 'control.stop', + 'control.stop', + 'foo.bar', + 'power.fake', + ], + ]) + ->assertOk(); + + $subuser->refresh(); + $this->assertEqualsCanonicalizing( + ['control.start', 'control.stop', 'websocket.connect'], + $subuser->permissions + ); + + Bus::assertDispatchedTimes(function (RevokeSftpAccessJob $job) use ($server, $subuser) { + return $job->user === $subuser->user->uuid && $job->target->is($server); + }); + } + + /** + * Ensure a subuser cannot assign permissions to an account that they do not have + * themselves. + */ + public function testUserCannotAssignPermissionsTheyDoNotHave() + { + Bus::fake([RevokeSftpAccessJob::class]); + + [$user, $server] = $this->generateTestAccount([Permission::ACTION_USER_READ, Permission::ACTION_USER_UPDATE]); + + $subuser = Subuser::factory() + ->for(User::factory()->create()) + ->for($server) + ->create(['permissions' => ['foo.bar']]); + + $this->actingAs($user) + ->postJson("/api/client/servers/$server->uuid/users/{$subuser->user->uuid}", [ + 'permissions' => [Permission::ACTION_USER_READ, Permission::ACTION_CONTROL_CONSOLE], + ]) + ->assertForbidden(); + + $this->assertEqualsCanonicalizing(['foo.bar'], $subuser->refresh()->permissions); + + Bus::assertNothingDispatched(); + } + + /** + * Test that a user cannot update thyself. + */ + public function testUserCannotUpdateSelf() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_USER_READ, Permission::ACTION_USER_UPDATE]); + + $this->actingAs($user) + ->postJson("/api/client/servers/$server->uuid/users/$user->uuid", []) + ->assertForbidden(); + } + + /** + * Test that an error is returned if you attempt to update a subuser on a different account. + */ + public function testCannotUpdateSubuserForDifferentServer() + { + [$user, $server] = $this->generateTestAccount(); + [$user2] = $this->generateTestAccount(['foo.bar']); + + $this->actingAs($user) + ->postJson("/api/client/servers/$server->uuid/users/$user2->uuid", []) + ->assertNotFound(); + } +} diff --git a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php index d2656015ea..76eee97bb1 100644 --- a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php +++ b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php @@ -4,6 +4,7 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Response; +use Pterodactyl\Enum\JwtScope; use Lcobucci\JWT\Configuration; use Pterodactyl\Models\Permission; use Lcobucci\JWT\Signer\Hmac\Sha256; @@ -14,14 +15,14 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase { /** - * Test that a subuser attempting to connect to the websocket recieves an error if they + * Test that a subuser attempting to connect to the websocket receives an error if they * do not explicitly have the permission. */ public function testSubuserWithoutWebsocketPermissionReceivesError() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_RESTART]); - $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket") + $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket") ->assertStatus(Response::HTTP_FORBIDDEN) ->assertJsonPath('errors.0.code', 'HttpForbiddenException') ->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.'); @@ -33,9 +34,9 @@ public function testSubuserWithoutWebsocketPermissionReceivesError() public function testUserWithoutPermissionForServerReceivesError() { [, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - [$user,] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); + [$user] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket") + $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket") ->assertStatus(Response::HTTP_NOT_FOUND); } @@ -53,17 +54,19 @@ public function testJwtAndWebsocketUrlAreReturnedForServerOwner() $server->node->scheme = 'https'; $server->node->save(); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket"); - - $response->assertOk(); - $response->assertJsonStructure(['data' => ['token', 'socket']]); + $response = $this->actingAs($user) + ->withoutExceptionHandling() + ->getJson("/api/client/servers/$server->uuid/websocket") + ->assertOk() + ->assertJsonStructure(['data' => ['token', 'socket']]); $connection = $response->json('data.socket'); $this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.'); - $this->assertStringEndsWith("/api/servers/{$server->uuid}/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.'); + $this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.'); $config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey())); - $config->setValidationConstraints(new SignedWith(new Sha256(), $key)); + $config = $config->withValidationConstraints(new SignedWith(new Sha256(), $key)); + /** @var \Lcobucci\JWT\Token\Plain $token */ $token = $config->parser()->parse($response->json('data.token')); @@ -86,9 +89,10 @@ public function testJwtAndWebsocketUrlAreReturnedForServerOwner() $this->assertEquals($expect, $token->claims()->get('iat')); $this->assertEquals($expect->subMinutes(5), $token->claims()->get('nbf')); $this->assertEquals($expect->addMinutes(10), $token->claims()->get('exp')); - $this->assertSame($user->id, $token->claims()->get('user_id')); + $this->assertSame($user->uuid, $token->claims()->get('user_uuid')); $this->assertSame($server->uuid, $token->claims()->get('server_uuid')); $this->assertSame(['*'], $token->claims()->get('permissions')); + $this->assertEquals(JwtScope::Websocket->value, $token->claims()->get('scope')); } /** @@ -102,13 +106,15 @@ public function testJwtIsConfiguredCorrectlyForServerSubuser() /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket"); - - $response->assertOk(); - $response->assertJsonStructure(['data' => ['token', 'socket']]); + $response = $this->actingAs($user) + ->withoutExceptionHandling() + ->getJson("/api/client/servers/$server->uuid/websocket") + ->assertOk() + ->assertJsonStructure(['data' => ['token', 'socket']]); $config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey())); - $config->setValidationConstraints(new SignedWith(new Sha256(), $key)); + $config = $config->withValidationConstraints(new SignedWith(new Sha256(), $key)); + /** @var \Lcobucci\JWT\Token\Plain $token */ $token = $config->parser()->parse($response->json('data.token')); @@ -119,5 +125,6 @@ public function testJwtIsConfiguredCorrectlyForServerSubuser() // Check that the claims are generated correctly. $this->assertSame($permissions, $token->claims()->get('permissions')); + $this->assertEquals(JwtScope::Websocket->value, $token->claims()->get('scope')); } } diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php index 903efb8531..5c3d80388d 100644 --- a/tests/Integration/Api/Client/TwoFactorControllerTest.php +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -17,7 +17,7 @@ class TwoFactorControllerTest extends ClientApiIntegrationTestCase */ public function testTwoFactorImageDataIsReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => false]); $this->assertFalse($user->use_totp); @@ -41,7 +41,7 @@ public function testTwoFactorImageDataIsReturned() */ public function testErrorIsReturnedWhenTwoFactorIsAlreadyEnabled() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => true]); $response = $this->actingAs($user)->getJson('/api/client/account/two-factor'); @@ -56,16 +56,16 @@ public function testErrorIsReturnedWhenTwoFactorIsAlreadyEnabled() */ public function testValidationErrorIsReturnedIfInvalidDataIsPassedToEnabled2FA() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => false]); - $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [ - 'code' => '', - ]); - - $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); - $response->assertJsonPath('errors.0.code', 'ValidationException'); - $response->assertJsonPath('errors.0.meta.rule', 'required'); + $this->actingAs($user) + ->postJson('/api/client/account/two-factor', ['code' => '']) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.rule', 'required') + ->assertJsonPath('errors.0.meta.source_field', 'code') + ->assertJsonPath('errors.1.meta.rule', 'required') + ->assertJsonPath('errors.1.meta.source_field', 'password'); } /** @@ -73,7 +73,7 @@ public function testValidationErrorIsReturnedIfInvalidDataIsPassedToEnabled2FA() */ public function testTwoFactorCanBeEnabledOnAccount() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => false]); // Make the initial call to get the account setup for 2FA. @@ -82,7 +82,7 @@ public function testTwoFactorCanBeEnabledOnAccount() $user = $user->refresh(); $this->assertNotNull($user->totp_secret); - /** @var \PragmaRX\Google2FA\Google2FA $service */ + /** @var Google2FA $service */ $service = $this->app->make(Google2FA::class); $secret = decrypt($user->totp_secret); @@ -90,6 +90,7 @@ public function testTwoFactorCanBeEnabledOnAccount() $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [ 'code' => $token, + 'password' => 'password', ]); $response->assertOk(); @@ -121,17 +122,17 @@ public function testTwoFactorCanBeEnabledOnAccount() } /** - * Test that two factor authentication can be disabled on an account as long as the password + * Test that two-factor authentication can be disabled on an account as long as the password * provided is valid for the account. */ public function testTwoFactorCanBeDisabledOnAccount() { Carbon::setTestNow(Carbon::now()); - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => true]); - $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [ + $response = $this->actingAs($user)->postJson('/api/client/account/two-factor/disable', [ 'password' => 'invalid', ]); @@ -139,7 +140,7 @@ public function testTwoFactorCanBeDisabledOnAccount() $response->assertJsonPath('errors.0.code', 'BadRequestHttpException'); $response->assertJsonPath('errors.0.detail', 'The password provided was not valid.'); - $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [ + $response = $this->actingAs($user)->postJson('/api/client/account/two-factor/disable', [ 'password' => 'password', ]); @@ -148,7 +149,7 @@ public function testTwoFactorCanBeDisabledOnAccount() $user = $user->refresh(); $this->assertFalse($user->use_totp); $this->assertNotNull($user->totp_authenticated_at); - $this->assertSame(Carbon::now()->toIso8601String(), $user->totp_authenticated_at->toIso8601String()); + $this->assertSame(Carbon::now()->toAtomString(), $user->totp_authenticated_at->toAtomString()); } /** @@ -159,13 +160,48 @@ public function testNoErrorIsReturnedIfTwoFactorIsNotEnabled() { Carbon::setTestNow(Carbon::now()); - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(['use_totp' => false]); - $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [ + $response = $this->actingAs($user)->postJson('/api/client/account/two-factor/disable', [ 'password' => 'password', ]); $response->assertStatus(Response::HTTP_NO_CONTENT); } + + /** + * Test that a valid account password is required when enabling two-factor. + */ + public function testEnablingTwoFactorRequiresValidPassword() + { + $user = User::factory()->create(['use_totp' => false]); + + $this->actingAs($user) + ->postJson('/api/client/account/two-factor', [ + 'code' => '123456', + 'password' => 'foo', + ]) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.detail', 'The password provided was not valid.'); + + $this->assertFalse($user->refresh()->use_totp); + } + + /** + * Test that a valid account password is required when disabling two-factor. + */ + public function testDisablingTwoFactorRequiresValidPassword() + { + $user = User::factory()->create(['use_totp' => true]); + + $this->actingAs($user) + ->postJson('/api/client/account/two-factor/disable', [ + 'password' => 'foo', + ]) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.detail', 'The password provided was not valid.'); + + $this->assertTrue($user->refresh()->use_totp); + } } diff --git a/tests/Integration/Api/Remote/ServerTransferControllerTest.php b/tests/Integration/Api/Remote/ServerTransferControllerTest.php new file mode 100644 index 0000000000..8b119a6515 --- /dev/null +++ b/tests/Integration/Api/Remote/ServerTransferControllerTest.php @@ -0,0 +1,111 @@ +createServerModel(); + + $new = Node::factory() + ->for(Location::factory()) + ->has(Allocation::factory()) + ->create(); + + $this->transfer = ServerTransfer::factory()->for($server)->create([ + 'old_allocation' => $server->allocation_id, + 'new_allocation' => $new->allocations->first()->id, + 'new_node' => $new->id, + 'old_node' => $server->node_id, + ]); + } + + public function testSuccessStatusUpdateCanBeSentFromNewNode(): void + { + $server = $this->transfer->server; + $newNode = $this->transfer->newNode; + + $this + ->withHeader('Authorization', "Bearer $newNode->daemon_token_id." . $newNode->getDecryptedKey()) + ->postJson("/api/remote/servers/{$server->uuid}/transfer/success") + ->assertNoContent(); + + $this->assertTrue($this->transfer->refresh()->successful); + } + + public function testFailureStatusUpdateCanBeSentFromOldNode(): void + { + $server = $this->transfer->server; + $oldNode = $this->transfer->oldNode; + + $this->withHeader('Authorization', "Bearer $oldNode->daemon_token_id." . $oldNode->getDecryptedKey()) + ->postJson("/api/remote/servers/{$server->uuid}/transfer/failure") + ->assertNoContent(); + + $this->assertFalse($this->transfer->refresh()->successful); + } + + public function testFailureStatusUpdateCanBeSentFromNewNode(): void + { + $server = $this->transfer->server; + $newNode = $this->transfer->newNode; + + $this->withHeader('Authorization', "Bearer $newNode->daemon_token_id." . $newNode->getDecryptedKey()) + ->postJson("/api/remote/servers/{$server->uuid}/transfer/failure") + ->assertNoContent(); + + $this->assertFalse($this->transfer->refresh()->successful); + } + + public function testSuccessStatusUpdateCannotBeSentFromOldNode(): void + { + $server = $this->transfer->server; + $oldNode = $this->transfer->oldNode; + + $this->withHeader('Authorization', "Bearer $oldNode->daemon_token_id." . $oldNode->getDecryptedKey()) + ->postJson("/api/remote/servers/{$server->uuid}/transfer/success") + ->assertForbidden() + ->assertJsonPath('errors.0.code', 'HttpForbiddenException') + ->assertJsonPath('errors.0.detail', 'Requesting node does not have permission to access this server.'); + + $this->assertNull($this->transfer->refresh()->successful); + } + + public function testSuccessStatusUpdateCannotBeSentFromUnauthorizedNode(): void + { + $server = $this->transfer->server; + $node = Node::factory()->for(Location::factory())->create(); + + $this->withHeader('Authorization', "Bearer $node->daemon_token_id." . $node->getDecryptedKey()) + ->postJson("/api/remote/servers/$server->uuid/transfer/success") + ->assertForbidden() + ->assertJsonPath('errors.0.code', 'HttpForbiddenException') + ->assertJsonPath('errors.0.detail', 'Requesting node does not have permission to access this server.'); + + $this->assertNull($this->transfer->refresh()->successful); + } + + public function testFailureStatusUpdateCannotBeSentFromUnauthorizedNode(): void + { + $server = $this->transfer->server; + $node = Node::factory()->for(Location::factory())->create(); + + $this->withHeader('Authorization', "Bearer $node->daemon_token_id." . $node->getDecryptedKey()) + ->postJson("/api/remote/servers/$server->uuid/transfer/failure")->assertForbidden() + ->assertJsonPath('errors.0.code', 'HttpForbiddenException') + ->assertJsonPath('errors.0.detail', 'Requesting node does not have permission to access this server.'); + + $this->assertNull($this->transfer->refresh()->successful); + } +} diff --git a/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php new file mode 100644 index 0000000000..c793435d69 --- /dev/null +++ b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php @@ -0,0 +1,246 @@ +generateTestAccount(); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->user = $user; + $this->server = $server; + + $this->setAuthorization(); + } + + /** + * Test that a public key is validated correctly. + */ + public function testPublicKeyIsValidatedCorrectly() + { + $key = UserSSHKey::factory()->for($this->user)->create(); + + $this->postJson('/api/remote/sftp/auth', []) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.source_field', 'username') + ->assertJsonPath('errors.0.meta.rule', 'required') + ->assertJsonPath('errors.1.meta.source_field', 'password') + ->assertJsonPath('errors.1.meta.rule', 'required'); + + $data = [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => $key->public_key, + ]; + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('server', $this->server->uuid) + ->assertJsonPath('permissions', ['*']); + + $key->delete(); + $this->postJson('/api/remote/sftp/auth', $data)->assertForbidden(); + $this->postJson('/api/remote/sftp/auth', array_merge($data, ['type' => null]))->assertForbidden(); + } + + /** + * Test that an account password is validated correctly. + */ + public function testPasswordIsValidatedCorrectly() + { + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => '', + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.source_field', 'password') + ->assertJsonPath('errors.0.meta.rule', 'required'); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => 'wrong password', + ]) + ->assertForbidden(); + + $this->user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => 'foobar', + ]) + ->assertOk(); + } + + /** + * Test that providing an invalid key and/or invalid username triggers the throttle on + * the endpoint. + */ + #[\PHPUnit\Framework\Attributes\DataProvider('authorizationTypeDataProvider')] + public function testUserIsThrottledIfInvalidCredentialsAreProvided() + { + for ($i = 0; $i <= 10; ++$i) { + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $i % 2 === 0 ? $this->user->username : $this->getUsername(), + 'password' => 'invalid key', + ]) + ->assertStatus($i === 10 ? 429 : 403); + } + } + + /** + * Test that the user is not throttled so long as a valid public key is provided, even + * if it doesn't actually exist in the database for the user. + */ + public function testUserIsNotThrottledIfNoPublicKeyMatches() + { + for ($i = 0; $i <= 10; ++$i) { + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => EC::createKey('Ed25519')->getPublicKey()->toString('OpenSSH'), + ]) + ->assertForbidden(); + } + } + + /** + * Test that a request is rejected if the credentials are valid but the username indicates + * a server on a different node. + */ + #[\PHPUnit\Framework\Attributes\DataProvider('authorizationTypeDataProvider')] + public function testRequestIsRejectedIfServerBelongsToDifferentNode(string $type) + { + $node2 = $this->createServerModel()->node; + + $this->setAuthorization($node2); + + $password = $type === 'public_key' + ? UserSSHKey::factory()->for($this->user)->create()->public_key + : 'foobar'; + + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => $password, + ]) + ->assertForbidden(); + } + + public function testRequestIsDeniedIfUserLacksSftpPermission() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ]); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->setAuthorization($server->node); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $user->username . '.' . $server->uuidShort, + 'password' => 'foobar', + ]) + ->assertForbidden() + ->assertJsonPath('errors.0.detail', 'You do not have permission to access SFTP for this server.'); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('serverStateDataProvider')] + public function testInvalidServerStateReturnsConflictError(string $status) + { + $this->server->update(['status' => $status]); + + $this->postJson('/api/remote/sftp/auth', ['username' => $this->getUsername(), 'password' => 'foobar']) + ->assertStatus(409); + } + + /** + * Test that permissions are returned for the user account correctly. + */ + public function testUserPermissionsAreReturnedCorrectly() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->setAuthorization($server->node); + + $data = [ + 'username' => $user->username . '.' . $server->uuidShort, + 'password' => 'foobar', + ]; + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions', [Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]); + + $user->update(['root_admin' => true]); + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions.0', '*'); + + $this->setAuthorization(); + $data['username'] = $user->username . '.' . $this->server->uuidShort; + + $this->post('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions.0', '*'); + + $user->update(['root_admin' => false]); + $this->post('/api/remote/sftp/auth', $data)->assertForbidden(); + } + + public static function authorizationTypeDataProvider(): array + { + return [ + 'password auth' => ['password'], + 'public key auth' => ['public_key'], + ]; + } + + public static function serverStateDataProvider(): array + { + return [ + 'installing' => [Server::STATUS_INSTALLING], + 'suspended' => [Server::STATUS_SUSPENDED], + 'restoring a backup' => [Server::STATUS_RESTORING_BACKUP], + ]; + } + + /** + * Returns the username for connecting to SFTP. + */ + protected function getUsername(bool $long = false): string + { + return $this->user->username . '.' . ($long ? $this->server->uuid : $this->server->uuidShort); + } + + /** + * Sets the authorization header for the rest of the test. + */ + protected function setAuthorization(?Node $node = null): void + { + $node = $node ?? $this->server->node; + + $this->withHeader('Authorization', 'Bearer ' . $node->daemon_token_id . '.' . decrypt($node->daemon_token)); + } +} diff --git a/tests/Integration/Http/Controllers/Admin/UserController/CreateUserTest.php b/tests/Integration/Http/Controllers/Admin/UserController/CreateUserTest.php new file mode 100644 index 0000000000..d4ddceb1ea --- /dev/null +++ b/tests/Integration/Http/Controllers/Admin/UserController/CreateUserTest.php @@ -0,0 +1,42 @@ +actingAs(User::factory()->create()) + ->post('/admin/users/new', [ + 'email' => 'test@example.com', + 'username' => 'testuser', + 'name_first' => 'Test', + 'name_last' => 'User', + ]) + ->assertForbidden(); + } + + public function testCreatingAdministratorAccountIsLogged(): void + { + $admin = User::factory()->admin()->create(); + + $this->actingAs($admin) + ->post('/admin/users/new', [ + 'email' => 'created.admin@example.com', + 'username' => 'createdadmin', + 'name_first' => 'Created', + 'name_last' => 'Admin', + 'root_admin' => 1, + ]) + ->assertSessionHasNoErrors(); + + /** @var User $created */ + $created = User::query()->where('username', 'createdadmin')->firstOrFail(); + $this->assertTrue($created->root_admin); + + $this->assertActivityFor('user:user.create', $admin, $created); + } +} diff --git a/tests/Integration/Http/Controllers/Admin/UserController/DeleteUserTest.php b/tests/Integration/Http/Controllers/Admin/UserController/DeleteUserTest.php new file mode 100644 index 0000000000..5768ae1a7a --- /dev/null +++ b/tests/Integration/Http/Controllers/Admin/UserController/DeleteUserTest.php @@ -0,0 +1,37 @@ +actingAs(User::factory()->create()) + ->delete(route('admin.users.delete', ['user' => User::factory()->create()])) + ->assertForbidden(); + } + + public function testCannotDeleteSelf(): void + { + $this->actingAs($user = User::factory()->admin()->create()) + ->delete(route('admin.users.delete', ['user' => $user])) + ->assertBadRequest() + ->assertJsonPath('errors.0.detail', __('admin/user.exceptions.delete_self')); + + $this->assertModelExists($user); + } + + public function testUserIsDeleted(): void + { + $user = User::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->delete(route('admin.users.delete', ['user' => $user])) + ->assertRedirectToRoute('admin.users'); + + $this->assertModelMissing($user); + } +} diff --git a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php b/tests/Integration/Http/Controllers/Admin/UserControllerTest.php deleted file mode 100644 index d101078330..0000000000 --- a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -create(['username' => $unique . '_1']), - User::factory()->create(['username' => $unique . '_2']), - ]; - - $servers = [ - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[1]->id]), - ]; - - Subuser::query()->forceCreate(['server_id' => $servers[0]->id, 'user_id' => $users[1]->id]); - Subuser::query()->forceCreate(['server_id' => $servers[1]->id, 'user_id' => $users[1]->id]); - - /** @var \Pterodactyl\Http\Controllers\Admin\UserController $controller */ - $controller = $this->app->make(UserController::class); - - $request = Request::create('/admin/users?filter[username]=' . $unique, 'GET'); - $this->app->instance(Request::class, $request); - - $data = $controller->index($request)->getData(); - $this->assertArrayHasKey('users', $data); - $this->assertInstanceOf(LengthAwarePaginator::class, $data['users']); - - /** @var \Pterodactyl\Models\User[] $response */ - $response = $data['users']->items(); - $this->assertCount(2, $response); - $this->assertInstanceOf(User::class, $response[0]); - $this->assertSame(3, (int) $response[0]->servers_count); - $this->assertSame(0, (int) $response[0]->subuser_of_count); - $this->assertSame(1, (int) $response[1]->servers_count); - $this->assertSame(2, (int) $response[1]->subuser_of_count); - } -} diff --git a/tests/Integration/Http/Controllers/Auth/LoginCheckpointControllerTest.php b/tests/Integration/Http/Controllers/Auth/LoginCheckpointControllerTest.php new file mode 100644 index 0000000000..a5aafcd4e1 --- /dev/null +++ b/tests/Integration/Http/Controllers/Auth/LoginCheckpointControllerTest.php @@ -0,0 +1,207 @@ +create([ + 'use_totp' => true, + 'totp_secret' => encrypt(str_repeat('a', 16)), + 'totp_authenticated_at' => is_null($ts) ? null : Carbon::now()->addSeconds($ts), + ]); + + Session::put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => 'token', + 'expires_at' => now()->addMinutes(5), + ]); + + $totp = $this->app->make(Google2FA::class)->getCurrentOtp(str_repeat('a', 16)); + + $this->withoutExceptionHandling()->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'authentication_code' => $totp, + ])) + ->assertOk() + ->assertSessionMissing('auth_confirmation_token') + ->assertJsonPath('data.complete', true) + ->assertJsonPath('data.intended', '/') + ->assertJsonPath('data.user.uuid', $user->uuid); + + $this->assertEquals(now(), $user->refresh()->totp_authenticated_at); + + $this->assertAuthenticatedAs($user); + + Event::assertDispatched(fn (DirectLogin $event) => $event->user->is($user) && $event->remember); + Event::assertDispatched(fn (ProvidedAuthenticationToken $event) => $event->user->is($user)); + } + + /** + * Test that a TOTP token cannot be reused by verifying that the OTP verification + * logic fails if the token's timestamp is before the `totp_authenticated_at` + * column value. + * + * @see https://github.com/pterodactyl/panel/security/advisories/GHSA-rgmp-4873-r683 + */ + #[TestWith([1])] + #[TestWith([30])] + #[TestWith([80])] + public function testTotpTokenCannotBeReused(int $seconds): void + { + $user = User::factory()->create([ + 'use_totp' => true, + 'totp_secret' => encrypt(str_repeat('a', 16)), + 'totp_authenticated_at' => now()->addSeconds($seconds), + ]); + + Session::put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => 'token', + 'expires_at' => now()->addMinutes(5), + ]); + + $totp = $this->app->make(Google2FA::class)->getCurrentOtp(str_repeat('a', 16)); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'authentication_code' => $totp, + ])) + ->assertBadRequest() + ->assertJsonPath('errors.0.detail', 'The two-factor authentication token was invalid.'); + + $this->assertGuest(); + $this->assertEquals(now()->addSeconds($seconds), $user->refresh()->totp_authenticated_at); + + Event::assertDispatched(fn (Failed $event) => $event->guard === 'auth' && $event->user->is($user)); + } + + public function testEndpointReturnsErrorIfSessionMissing(): void + { + $this->postJson(route('auth.login-checkpoint')) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.source_field', 'confirmation_token') + ->assertJsonPath('errors.1.meta.source_field', 'authentication_code') + ->assertJsonPath('errors.2.meta.source_field', 'recovery_token'); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'authentication_code' => '123456', + ])) + ->assertBadRequest() + ->assertJsonPath('errors.0.detail', 'The authentication token provided has expired, please refresh the page and try again.'); + + $this->assertGuest(); + + Event::assertDispatched(fn (Failed $event) => $event->guard === 'auth'); + } + + public function testEndpointAppliesThrottling(): void + { + for ($i = 0; $i < 5; ++$i) { + $this->postJson(route('auth.login-checkpoint', ['confirmation_token' => 'token', 'authentication_code' => '123456'])) + ->assertBadRequest(); + } + + $this->postJson(route('auth.login-checkpoint', ['confirmation_token' => 'token', 'authentication_code' => '123456'])) + ->assertTooManyRequests(); + } + + public function testEndpointBlocksSessionDataMismatch(): void + { + $user = User::factory()->create([ + 'use_totp' => true, + 'totp_secret' => str_repeat('a', 16), + ]); + + Session::put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => 'token', + 'expires_at' => now()->addMinutes(5), + ]); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'wrong-token', + 'authentication_code' => $this->app->make(Google2FA::class)->getCurrentOtp(str_repeat('a', 16)), + ])) + ->assertBadRequest(); + + $this->assertGuest(); + + Event::assertDispatched(Failed::class); + } + + public function testEndpointReturnsErrorIfUserDoesNotExist(): void + { + Session::put('auth_confirmation_token', [ + 'user_id' => 0, + 'token_value' => 'token', + 'expires_at' => now()->addMinutes(5), + ]); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'authentication_code' => '123456', + ])) + ->assertBadRequest() + ->assertJsonPath('errors.0.detail', 'The authentication token provided has expired, please refresh the page and try again.'); + } + + public function testEndpointAllowsRecoveryToken(): void + { + $user = User::factory()->create(); + $token = $user->recoveryTokens()->forceCreate(['token' => password_hash('recovery', PASSWORD_DEFAULT)]); + + Session::put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => 'token', + 'expires_at' => now()->addMinutes(5), + ]); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'recovery_token' => 'invalid', + ])) + ->assertBadRequest() + ->assertJsonPath('errors.0.detail', 'The recovery token provided is not valid.'); + + $this->assertGuest(); + + $this->postJson(route('auth.login-checkpoint', [ + 'confirmation_token' => 'token', + 'recovery_token' => 'recovery', + ])) + ->assertOk() + ->assertSessionMissing('auth_confirmation_token'); + + Event::assertDispatched(ProvidedAuthenticationToken::class); + Event::assertDispatched(DirectLogin::class); + } +} diff --git a/tests/Integration/Http/HttpTestCase.php b/tests/Integration/Http/HttpTestCase.php new file mode 100644 index 0000000000..e3a6621ace --- /dev/null +++ b/tests/Integration/Http/HttpTestCase.php @@ -0,0 +1,9 @@ + 'application/json', + ]; - /** - * Setup base integration test cases. - */ public function setUp(): void { parent::setUp(); - // Disable event dispatcher to prevent eloquence from trying to - // perform validation on models going into the database. If this is - // not disabled, eloquence validation errors get swallowed and - // the tests cannot complete because nothing is put into the database. - Model::unsetEventDispatcher(); - } - - /** - * @return array - */ - protected function connectionsToTransact() - { - return ['testing']; + Event::fake(ActivityLogged::class); } /** @@ -39,8 +34,8 @@ protected function connectionsToTransact() */ protected function formatTimestamp(string $timestamp): string { - return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) + return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) ->setTimezone(BaseTransformer::RESPONSE_TIMEZONE) - ->toIso8601String(); + ->toAtomString(); } } diff --git a/tests/Integration/Jobs/RevokeSftpAccessJobTest.php b/tests/Integration/Jobs/RevokeSftpAccessJobTest.php new file mode 100644 index 0000000000..90515a3979 --- /dev/null +++ b/tests/Integration/Jobs/RevokeSftpAccessJobTest.php @@ -0,0 +1,70 @@ +make(['uuid' => 'uuid-1234']); + + $job = new RevokeSftpAccessJob('user-1', $model); + + $this->assertEquals( + "revoke-sftp:user-1:{$key}:uuid-1234", + $job->uniqueId() + ); + } + + public function testJobReleasesBackToQueueOnFailure(): void + { + $node = Node::factory()->make(['uuid' => 'uuid-1234']); + + $mock = $this->mock(DaemonRevocationRepository::class, function ($mock) { + $mock->expects('setNode->deauthorize')->andThrows( + new DaemonConnectionException(new TransferException('Connection failed')) + ); + }); + + $job = \Mockery::mock(RevokeSftpAccessJob::class, ['user-1', $node])->makePartial(); + $job->expects('release')->with(10); + + $job->handle($mock); + } + + public function testJobDispatchesForNode(): void + { + $node = Node::factory()->make(['uuid' => 'uuid-1234']); + + $mock = $this->mock(DaemonRevocationRepository::class, function ($mock) { + $mock->expects('setNode')->andReturnSelf(); + $mock->expects('deauthorize')->with('user-1', [])->andReturnUndefined(); + }); + + (new RevokeSftpAccessJob('user-1', $node))->handle($mock); + } + + public function testJobDispatchesForIndividualServer(): void + { + $node = Node::factory()->make(['uuid' => 'node-1234']); + $server = Server::factory()->make(['uuid' => 'server-1234'])->setRelation('node', $node); + + $mock = $this->mock(DaemonRevocationRepository::class, function ($mock) { + $mock->expects('setNode')->with(\Mockery::on(fn (Node $node) => $node->uuid === 'node-1234'))->andReturnSelf(); + $mock->expects('deauthorize')->with('user-1', ['server-1234'])->andReturnUndefined(); + }); + + (new RevokeSftpAccessJob('user-1', $server))->handle($mock); + } +} diff --git a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php index 68249dc43c..907454a84a 100644 --- a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php +++ b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php @@ -2,12 +2,11 @@ namespace Pterodactyl\Tests\Integration\Jobs\Schedule; -use Mockery; +use Carbon\Carbon; use Carbon\CarbonImmutable; use GuzzleHttp\Psr7\Request; use Pterodactyl\Models\Task; use GuzzleHttp\Psr7\Response; -use InvalidArgumentException; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Support\Facades\Bus; @@ -26,19 +25,19 @@ public function testInactiveJobIsNotRun() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create([ 'server_id' => $server->id, 'is_processing' => true, 'last_run_at' => null, 'is_active' => false, ]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'is_queued' => true]); $job = new RunTaskJob($task); - Bus::dispatchNow($job); + Bus::dispatchSync($job); $task->refresh(); $schedule->refresh(); @@ -46,40 +45,38 @@ public function testInactiveJobIsNotRun() $this->assertFalse($task->is_queued); $this->assertFalse($schedule->is_processing); $this->assertFalse($schedule->is_active); - $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at)); } public function testJobWithInvalidActionThrowsException() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'action' => 'foobar']); $job = new RunTaskJob($task); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid task action provided: foobar'); - Bus::dispatchNow($job); + Bus::dispatchSync($job); } - /** - * @dataProvider isManualRunDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('isManualRunDataProvider')] public function testJobIsExecuted(bool $isManualRun) { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create([ 'server_id' => $server->id, 'is_active' => !$isManualRun, 'is_processing' => true, 'last_run_at' => null, ]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create([ 'schedule_id' => $schedule->id, 'action' => Task::ACTION_POWER, @@ -88,34 +85,32 @@ public function testJobIsExecuted(bool $isManualRun) 'continue_on_failure' => false, ]); - $mock = Mockery::mock(DaemonPowerRepository::class); + $mock = \Mockery::mock(DaemonPowerRepository::class); $this->instance(DaemonPowerRepository::class, $mock); - $mock->expects('setServer')->with(Mockery::on(function ($value) use ($server) { + $mock->expects('setServer')->with(\Mockery::on(function ($value) use ($server) { return $value instanceof Server && $value->id === $server->id; }))->andReturnSelf(); $mock->expects('send')->with('start')->andReturn(new Response()); - Bus::dispatchNow(new RunTaskJob($task, $isManualRun)); + Bus::dispatchSync(new RunTaskJob($task, $isManualRun)); $task->refresh(); $schedule->refresh(); $this->assertFalse($task->is_queued); $this->assertFalse($schedule->is_processing); - $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at)); } - /** - * @dataProvider isManualRunDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('isManualRunDataProvider')] public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure) { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create([ 'schedule_id' => $schedule->id, 'action' => Task::ACTION_POWER, @@ -123,7 +118,7 @@ public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure 'continue_on_failure' => $continueOnFailure, ]); - $mock = Mockery::mock(DaemonPowerRepository::class); + $mock = \Mockery::mock(DaemonPowerRepository::class); $this->instance(DaemonPowerRepository::class, $mock); $mock->expects('setServer->send')->andThrow( @@ -134,7 +129,7 @@ public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure $this->expectException(DaemonConnectionException::class); } - Bus::dispatchNow(new RunTaskJob($task)); + Bus::dispatchSync(new RunTaskJob($task)); if ($continueOnFailure) { $task->refresh(); @@ -142,14 +137,41 @@ public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure $this->assertFalse($task->is_queued); $this->assertFalse($schedule->is_processing); - $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at)); } } /** - * @return array + * Test that a schedule is not executed if the server is suspended. + * + * @see https://github.com/pterodactyl/panel/issues/4008 */ - public function isManualRunDataProvider() + public function testTaskIsNotRunIfServerIsSuspended() + { + $server = $this->createServerModel([ + 'status' => Server::STATUS_SUSPENDED, + ]); + + $schedule = Schedule::factory()->for($server)->create([ + 'last_run_at' => Carbon::now()->subHour(), + ]); + + $task = Task::factory()->for($schedule)->create([ + 'action' => Task::ACTION_POWER, + 'payload' => 'start', + ]); + + Bus::dispatchSync(new RunTaskJob($task)); + + $task->refresh(); + $schedule->refresh(); + + $this->assertFalse($task->is_queued); + $this->assertFalse($schedule->is_processing); + $this->assertTrue(Carbon::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at)); + } + + public static function isManualRunDataProvider(): array { return [[true], [false]]; } diff --git a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php index c3b88e2cf9..3c116cbebb 100644 --- a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php +++ b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Tests\Integration\Services\Allocations; -use Exception; -use InvalidArgumentException; use Pterodactyl\Models\Allocation; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Pterodactyl\Services\Allocations\FindAssignableAllocationService; @@ -25,7 +23,7 @@ public function setUp(): void } /** - * Test that an unassigned allocation is prefered rather than creating an entirely new + * Test that an unassigned allocation is preferred rather than creating an entirely new * allocation for the server. */ public function testExistingAllocationIsPreferred() @@ -141,9 +139,9 @@ public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() try { $this->getService()->handle($server); - $this->assertTrue(false, 'This assertion should not be reached.'); - } catch (Exception $exception) { - $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->fail('This assertion should not be reached.'); + } catch (\Exception $exception) { + $this->assertInstanceOf(\InvalidArgumentException::class, $exception); $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); } @@ -152,9 +150,9 @@ public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() try { $this->getService()->handle($server); - $this->assertTrue(false, 'This assertion should not be reached.'); - } catch (Exception $exception) { - $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->fail('This assertion should not be reached.'); + } catch (\Exception $exception) { + $this->assertInstanceOf(\InvalidArgumentException::class, $exception); $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); } } @@ -169,10 +167,7 @@ public function testExceptionIsThrownIfFeatureIsNotEnabled() $this->getService()->handle($server); } - /** - * @return \Pterodactyl\Services\Allocations\FindAssignableAllocationService - */ - private function getService() + private function getService(): FindAssignableAllocationService { return $this->app->make(FindAssignableAllocationService::class); } diff --git a/tests/Integration/Services/Backups/DeleteBackupServiceTest.php b/tests/Integration/Services/Backups/DeleteBackupServiceTest.php index 34d4028bd9..d1fb0faac2 100644 --- a/tests/Integration/Services/Backups/DeleteBackupServiceTest.php +++ b/tests/Integration/Services/Backups/DeleteBackupServiceTest.php @@ -2,12 +2,12 @@ namespace Pterodactyl\Tests\Integration\Services\Backups; -use Mockery; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Backup; use GuzzleHttp\Exception\ClientException; use Pterodactyl\Extensions\Backups\BackupManager; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; use Pterodactyl\Services\Backups\DeleteBackupService; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; @@ -16,17 +16,6 @@ class DeleteBackupServiceTest extends IntegrationTestCase { - private $repository; - - public function setUp(): void - { - parent::setUp(); - - $this->repository = Mockery::mock(DaemonBackupRepository::class); - - $this->app->instance(DaemonBackupRepository::class, $this->repository); - } - public function testLockedBackupCannotBeDeleted() { $server = $this->createServerModel(); @@ -49,9 +38,8 @@ public function testFailedBackupThatIsLockedCanBeDeleted() 'is_successful' => false, ]); - $this->repository->expects('setServer->delete')->with($backup)->andReturn( - new Response() - ); + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andReturn(new Response()); $this->app->make(DeleteBackupService::class)->handle($backup); @@ -65,7 +53,8 @@ public function testExceptionThrownDueToMissingBackupIsIgnored() $server = $this->createServerModel(); $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with($backup)->andThrow( + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andThrow( new DaemonConnectionException( new ClientException('', new Request('DELETE', '/'), new Response(404)) ) @@ -83,7 +72,8 @@ public function testExceptionIsThrownIfNot404() $server = $this->createServerModel(); $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with($backup)->andThrow( + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andThrow( new DaemonConnectionException( new ClientException('', new Request('DELETE', '/'), new Response(500)) ) @@ -107,17 +97,18 @@ public function testS3ObjectCanBeDeleted() ]); $manager = $this->mock(BackupManager::class); - $manager->expects('getBucket')->andReturns('foobar'); - $manager->expects('adapter')->with(Backup::ADAPTER_AWS_S3)->andReturnSelf(); - $manager->expects('getClient->deleteObject')->with([ + $adapter = $this->mock(S3Filesystem::class); + + $manager->expects('adapter')->with(Backup::ADAPTER_AWS_S3)->andReturn($adapter); + + $adapter->expects('getBucket')->andReturn('foobar'); + $adapter->expects('getClient->deleteObject')->with([ 'Bucket' => 'foobar', 'Key' => sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid), ]); $this->app->make(DeleteBackupService::class)->handle($backup); - $backup->refresh(); - - $this->assertNotNull($backup->deleted_at); + $this->assertSoftDeleted($backup); } } diff --git a/tests/Integration/Services/Backups/DownloadLinkServiceTest.php b/tests/Integration/Services/Backups/DownloadLinkServiceTest.php new file mode 100644 index 0000000000..94535a70d7 --- /dev/null +++ b/tests/Integration/Services/Backups/DownloadLinkServiceTest.php @@ -0,0 +1,55 @@ +createServerModel(); + $backup = Backup::factory()->for($server)->create([ + 'disk' => Backup::ADAPTER_WINGS, + ]); + + $url = $this->app->make(DownloadLinkService::class)->handle($backup, $server->user); + + $this->assertStringStartsWith($prefix = $server->node->getConnectionAddress() . '/download/backup?token=', $url); + + $config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey())); + $config = $config->withValidationConstraints(new SignedWith(new Sha256(), $key)); + + /** @var \Lcobucci\JWT\Token\Plain $token */ + $token = $config->parser()->parse(substr($url, strlen($prefix))); + + $this->assertTrue( + $config->validator()->validate($token, ...$config->validationConstraints()), + 'Failed to validate that the JWT data returned was signed using the Node\'s secret key.' + ); + + $timestamp = CarbonImmutable::createFromTimestamp(CarbonImmutable::now()->getTimestamp())->timezone('UTC'); + + // Check that the claims are generated correctly. + $this->assertTrue($token->hasBeenIssuedBy(config('app.url'))); + $this->assertTrue($token->isPermittedFor($server->node->getConnectionAddress())); + $this->assertEquals($timestamp, $token->claims()->get('iat')); + $this->assertEquals($timestamp->subMinutes(5), $token->claims()->get('nbf')); + $this->assertEquals($timestamp->addMinutes(15), $token->claims()->get('exp')); + $this->assertSame($backup->uuid, $token->claims()->get('backup_uuid')); + $this->assertSame($server->uuid, $token->claims()->get('server_uuid')); + $this->assertEquals(JwtScope::BackupDownload->value, $token->claims()->get('scope')); + } +} diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index e354e02995..7ccfb83380 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -2,9 +2,7 @@ namespace Pterodactyl\Tests\Integration\Services\Databases; -use Mockery; -use BadMethodCallException; -use InvalidArgumentException; +use Mockery\MockInterface; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\IntegrationTestCase; @@ -16,8 +14,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; + private MockInterface $repository; /** * Setup tests. @@ -28,8 +25,7 @@ public function setUp(): void config()->set('pterodactyl.client_features.databases.enabled', true); - $this->repository = Mockery::mock(DatabaseRepository::class); - $this->swap(DatabaseRepository::class, $this->repository); + $this->repository = $this->mock(DatabaseRepository::class); } /** @@ -73,15 +69,13 @@ public function testDatabaseCannotBeCreatedIfServerHasReachedLimit() /** * Test that a missing or invalid database name format causes an exception to be thrown. - * - * @param array $data - * @dataProvider invalidDataDataProvider */ - public function testEmptyDatabaseNameOrInvalidNameTriggersAnException($data) + #[\PHPUnit\Framework\Attributes\DataProvider('invalidDataDataProvider')] + public function testEmptyDatabaseNameOrInvalidNameTriggersAnException(array $data) { $server = $this->createServerModel(); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".'); $this->getService()->create($server, $data); @@ -136,13 +130,13 @@ public function testServerDatabaseCanBeCreated() // assertions that would get caught by the functions catcher and thus lead to the exception // being swallowed incorrectly. $this->repository->expects('createUser')->with( - Mockery::on(function ($value) use (&$username) { + \Mockery::on(function ($value) use (&$username) { $username = $value; return true; }), '%', - Mockery::on(function ($value) use (&$password) { + \Mockery::on(function ($value) use (&$password) { $password = $value; return true; @@ -150,7 +144,7 @@ public function testServerDatabaseCanBeCreated() null ); - $this->repository->expects('assignUserToDatabase')->with($name, Mockery::on(function ($value) use (&$secondUsername) { + $this->repository->expects('assignUserToDatabase')->with($name, \Mockery::on(function ($value) use (&$secondUsername) { $secondUsername = $value; return true; @@ -166,7 +160,7 @@ public function testServerDatabaseCanBeCreated() $this->assertInstanceOf(Database::class, $response); $this->assertSame($response->server_id, $server->id); - $this->assertMatchesRegularExpression('/^(u[\d]+_)(\w){10}$/', $username); + $this->assertMatchesRegularExpression('/^(u\d+_)(\w){10}$/', $username); $this->assertSame($username, $secondUsername); $this->assertSame(24, strlen($password)); @@ -174,8 +168,8 @@ public function testServerDatabaseCanBeCreated() } /** - * Test that an exception encountered while creating the database leads to cleanup code being called - * and any exceptions encountered while cleaning up go unreported. + * Test that an exception encountered while creating the database leads to the cleanup code + * being called and any exceptions encountered while cleaning up go unreported. */ public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() { @@ -184,11 +178,11 @@ public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); - $this->repository->expects('createDatabase')->with($name)->andThrows(new BadMethodCallException()); + $this->repository->expects('createDatabase')->with($name)->andThrows(new \BadMethodCallException()); $this->repository->expects('dropDatabase')->with($name); - $this->repository->expects('dropUser')->withAnyArgs()->andThrows(new InvalidArgumentException()); + $this->repository->expects('dropUser')->withAnyArgs()->andThrows(new \InvalidArgumentException()); - $this->expectException(BadMethodCallException::class); + $this->expectException(\BadMethodCallException::class); $this->getService()->create($server, [ 'remote' => '%', @@ -199,7 +193,7 @@ public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() $this->assertDatabaseMissing('databases', ['server_id' => $server->id]); } - public function invalidDataDataProvider(): array + public static function invalidDataDataProvider(): array { return [ [[]], @@ -211,10 +205,7 @@ public function invalidDataDataProvider(): array ]; } - /** - * @return \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private function getService() + private function getService(): DatabaseManagementService { return $this->app->make(DatabaseManagementService::class); } diff --git a/tests/Integration/Services/Databases/DatabasePasswordServiceTest.php b/tests/Integration/Services/Databases/DatabasePasswordServiceTest.php new file mode 100644 index 0000000000..26f72c592d --- /dev/null +++ b/tests/Integration/Services/Databases/DatabasePasswordServiceTest.php @@ -0,0 +1,77 @@ +repository = $this->mock(DatabaseRepository::class); + } + + /** + * Test that a database password is rotated correctly. + */ + public function testDatabasePasswordCanBeRotated() + { + $server = $this->createServerModel(); + $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + + $database = Database::factory()->create([ + 'server_id' => $server->id, + 'database_host_id' => $host->id, + 'password' => encrypt('original'), + ]); + + $other = Database::factory()->create([ + 'server_id' => $server->id, + 'database_host_id' => $host->id, + 'password' => encrypt('unchanged'), + ]); + + $password = null; + + $this->repository->expects('dropUser')->with($database->username, $database->remote); + $this->repository->expects('createUser')->with( + $database->username, + $database->remote, + \Mockery::on(function ($value) use (&$password) { + $password = $value; + + return true; + }), + $database->max_connections + ); + $this->repository->expects('assignUserToDatabase')->with($database->database, $database->username, $database->remote); + $this->repository->expects('flush')->withNoArgs(); + + $response = $this->getService()->handle($database); + + // The new password is returned, set on the host, and stored. + $this->assertSame(24, strlen($response)); + $this->assertSame($response, $password); + $this->assertSame($response, decrypt($database->refresh()->password)); + + // Other databases are untouched. + $this->assertSame('unchanged', decrypt($other->refresh()->password)); + } + + private function getService(): DatabasePasswordService + { + return $this->app->make(DatabasePasswordService::class); + } +} diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 2d47195719..f9afd85643 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -2,9 +2,8 @@ namespace Pterodactyl\Tests\Integration\Services\Databases; -use Mockery; +use Mockery\MockInterface; use Pterodactyl\Models\Node; -use InvalidArgumentException; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\IntegrationTestCase; @@ -14,8 +13,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $managementService; + private MockInterface $managementService; /** * Setup tests. @@ -24,7 +22,7 @@ public function setUp(): void { parent::setUp(); - $this->managementService = Mockery::mock(DatabaseManagementService::class); + $this->managementService = \Mockery::mock(DatabaseManagementService::class); $this->swap(DatabaseManagementService::class, $this->managementService); } @@ -43,16 +41,14 @@ protected function tearDown(): void /** * Test that an error is thrown if either the database name or the remote host are empty. - * - * @param array $data - * @dataProvider invalidDataProvider */ - public function testErrorIsThrownIfDatabaseNameIsEmpty($data) + #[\PHPUnit\Framework\Attributes\DataProvider('invalidDataProvider')] + public function testErrorIsThrownIfDatabaseNameIsEmpty(array $data) { $server = $this->createServerModel(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/^Expected a non-empty value\. Got: /', ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/^Expected a non-empty value\. Got: /'); $this->getService()->handle($server, $data); } @@ -143,7 +139,7 @@ public function testDatabaseHostIsSelectedIfNoSuitableHostExistsOnSameNode() $this->assertInstanceOf(Database::class, $response); } - public function invalidDataProvider(): array + public static function invalidDataProvider(): array { return [ [['remote' => '%']], @@ -154,10 +150,7 @@ public function invalidDataProvider(): array ]; } - /** - * @return \Pterodactyl\Services\Databases\DeployServerDatabaseService - */ - private function getService() + private function getService(): DeployServerDatabaseService { return $this->app->make(DeployServerDatabaseService::class); } diff --git a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php b/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php index f32810cc87..64594c8b63 100644 --- a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php +++ b/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php @@ -2,9 +2,7 @@ namespace Pterodactyl\Tests\Integration\Services\Deployment; -use Exception; use Pterodactyl\Models\Node; -use InvalidArgumentException; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Models\Location; @@ -26,7 +24,7 @@ public function setUp(): void public function testExceptionIsThrownIfNoDiskSpaceHasBeenSet() { - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Disk space must be an int, got NULL'); $this->getService()->handle(); @@ -34,7 +32,7 @@ public function testExceptionIsThrownIfNoDiskSpaceHasBeenSet() public function testExceptionIsThrownIfNoMemoryHasBeenSet() { - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Memory usage must be an int, got NULL'); $this->getService()->setDisk(10)->handle(); @@ -53,17 +51,17 @@ public function testNoExceptionIsThrownIfStringifiedIntegersArePassedForLocation try { $this->getService()->setLocations(['a']); - $this->assertTrue(false, 'This expectation should not be called.'); - } catch (Exception $exception) { - $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->fail('This expectation should not be called.'); + } catch (\Exception $exception) { + $this->assertInstanceOf(\InvalidArgumentException::class, $exception); $this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage()); } try { $this->getService()->setLocations(['1.2', '1', 2]); - $this->assertTrue(false, 'This expectation should not be called.'); - } catch (Exception $exception) { - $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->fail('This expectation should not be called.'); + } catch (\Exception $exception) { + $this->assertInstanceOf(\InvalidArgumentException::class, $exception); $this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage()); } } @@ -96,7 +94,7 @@ public function testExpectedNodeIsReturnedForLocation() ]), ]; - // Expect that all of the nodes are returned as we're under all of their limits + // Expect that all the nodes are returned as we're under all of their limits // and there is no location filter being provided. $response = $this->getService()->setDisk(512)->setMemory(512)->handle(); $this->assertInstanceOf(Collection::class, $response); @@ -182,10 +180,7 @@ public function testExpectedNodeIsReturnedForLocation() $this->assertSame($nodes[1]->id, $response[0]->id); } - /** - * @return \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private function getService() + private function getService(): FindViableNodesService { return $this->app->make(FindViableNodesService::class); } diff --git a/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php b/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php index e5dd9e234e..29c91f4633 100644 --- a/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php +++ b/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php @@ -2,11 +2,9 @@ namespace Pterodactyl\Tests\Integration\Services\Schedules; -use Mockery; use Exception; use Carbon\CarbonImmutable; use Pterodactyl\Models\Task; -use InvalidArgumentException; use Pterodactyl\Models\Schedule; use Illuminate\Support\Facades\Bus; use Illuminate\Contracts\Bus\Dispatcher; @@ -38,16 +36,16 @@ public function testErrorDuringScheduleDataUpdateDoesNotPersistChanges() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create([ 'server_id' => $server->id, 'cron_minute' => 'hodor', // this will break the getNextRunDate() function. ]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->getService()->handle($schedule); @@ -57,20 +55,18 @@ public function testErrorDuringScheduleDataUpdateDoesNotPersistChanges() /** * Test that a job is dispatched as expected using the initial delay. - * - * @param bool $now - * @dataProvider dispatchNowDataProvider */ - public function testJobCanBeDispatchedWithExpectedInitialDelay($now) + #[\PHPUnit\Framework\Attributes\DataProvider('dispatchNowDataProvider')] + public function testJobCanBeDispatchedWithExpectedInitialDelay(bool $now) { Bus::fake(); $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'time_offset' => 10, 'sequence_id' => 1]); $this->getService()->handle($schedule, $now); @@ -99,10 +95,10 @@ public function testFirstSequenceTaskIsFound() Bus::fake(); $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task2 = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 4]); $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 2]); $task3 = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 3]); @@ -127,17 +123,17 @@ public function testFirstSequenceTaskIsFound() */ public function testTaskDispatchedNowIsResetProperlyIfErrorIsEncountered() { - $this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class)); + $this->swap(Dispatcher::class, $dispatcher = \Mockery::mock(Dispatcher::class)); $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Schedule $schedule */ + /** @var Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server->id, 'last_run_at' => null]); - /** @var \Pterodactyl\Models\Task $task */ + /** @var Task $task */ $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]); - $dispatcher->expects('dispatchNow')->andThrows(new Exception('Test thrown exception')); + $dispatcher->expects('dispatchNow')->andThrows(new \Exception('Test thrown exception')); - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $this->expectExceptionMessage('Test thrown exception'); $this->getService()->handle($schedule, true); @@ -151,15 +147,12 @@ public function testTaskDispatchedNowIsResetProperlyIfErrorIsEncountered() $this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => false]); } - public function dispatchNowDataProvider(): array + public static function dispatchNowDataProvider(): array { return [[true], [false]]; } - /** - * @return \Pterodactyl\Services\Schedules\ProcessScheduleService - */ - private function getService() + private function getService(): ProcessScheduleService { return $this->app->make(ProcessScheduleService::class); } diff --git a/tests/Integration/Services/Servers/BuildModificationServiceTest.php b/tests/Integration/Services/Servers/BuildModificationServiceTest.php index cadc9276dd..52d69685c2 100644 --- a/tests/Integration/Services/Servers/BuildModificationServiceTest.php +++ b/tests/Integration/Services/Servers/BuildModificationServiceTest.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; -use Mockery; +use Mockery\MockInterface; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Server; @@ -16,8 +16,7 @@ class BuildModificationServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + private MockInterface $daemonServerRepository; /** * Setup tests. @@ -108,7 +107,7 @@ public function testServerBuildDataIsProperlyUpdatedOnWings() { $server = $this->createServerModel(); - $this->daemonServerRepository->expects('setServer')->with(Mockery::on(function (Server $s) use ($server) { + $this->daemonServerRepository->expects('setServer')->with(\Mockery::on(function (Server $s) use ($server) { return $s->id === $server->id; }))->andReturnSelf(); @@ -169,7 +168,7 @@ public function testConnectionExceptionIsIgnoredWhenUpdatingServerSettings() public function testNoExceptionIsThrownIfOnlyRemovingAllocation() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create(['node_id' => $server->node_id, 'server_id' => $server->id]); $this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined(); @@ -183,7 +182,7 @@ public function testNoExceptionIsThrownIfOnlyRemovingAllocation() /** * Test that allocations in both the add and remove arrays are only added, and not removed. - * This scenario wouldn't really happen in the UI, but it is possible to perform via the API + * This scenario wouldn't really happen in the UI, but it is possible to perform via the API, * so we want to make sure that the logic being used doesn't break if the allocation exists * in both arrays. * @@ -192,7 +191,7 @@ public function testNoExceptionIsThrownIfOnlyRemovingAllocation() public function testAllocationInBothAddAndRemoveIsAdded() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create(['node_id' => $server->node_id]); $this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined(); @@ -211,9 +210,9 @@ public function testAllocationInBothAddAndRemoveIsAdded() public function testUsingSameAllocationIdMultipleTimesDoesNotError() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create(['node_id' => $server->node_id, 'server_id' => $server->id]); - /** @var \Pterodactyl\Models\Allocation $allocation2 */ + /** @var Allocation $allocation2 */ $allocation2 = Allocation::factory()->create(['node_id' => $server->node_id]); $this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined(); @@ -229,14 +228,14 @@ public function testUsingSameAllocationIdMultipleTimesDoesNotError() /** * Test that any changes we made to the server or allocations are rolled back if there is an - * exception while performing any action. This is different than the connection exception + * exception while performing any action. This is different from the connection exception * test which should properly ignore connection issues. We want any other type of exception * to properly be thrown back to the caller. */ public function testThatUpdatesAreRolledBackIfExceptionIsEncountered() { $server = $this->createServerModel(); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create(['node_id' => $server->node_id]); $this->daemonServerRepository->expects('setServer->sync')->andThrows(new DisplayException('Test')); @@ -248,10 +247,7 @@ public function testThatUpdatesAreRolledBackIfExceptionIsEncountered() $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null]); } - /** - * @return \Pterodactyl\Services\Servers\BuildModificationService - */ - private function getService() + private function getService(): BuildModificationService { return $this->app->make(BuildModificationService::class); } diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index 329e3be1d1..e840bb30da 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; -use Mockery; +use Mockery\MockInterface; use Pterodactyl\Models\Egg; use GuzzleHttp\Psr7\Request; use Pterodactyl\Models\Node; @@ -24,8 +24,9 @@ class ServerCreationServiceTest extends IntegrationTestCase { use WithFaker; - /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + protected MockInterface $daemonServerRepository; + + protected Egg $bungeecord; /** * Stub the calls to Wings so that we don't actually hit those API endpoints. @@ -34,7 +35,13 @@ public function setUp(): void { parent::setUp(); - $this->daemonServerRepository = Mockery::mock(DaemonServerRepository::class); + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->bungeecord = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + + $this->daemonServerRepository = \Mockery::mock(DaemonServerRepository::class); $this->swap(DaemonServerRepository::class, $this->daemonServerRepository); } @@ -47,13 +54,13 @@ public function setUp(): void */ public function testServerIsCreatedWithDeploymentObject() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\Location $location */ + /** @var Location $location */ $location = Location::factory()->create(); - /** @var \Pterodactyl\Models\Node $node */ + /** @var Node $node */ $node = Node::factory()->create([ 'location_id' => $location->id, ]); @@ -67,7 +74,7 @@ public function testServerIsCreatedWithDeploymentObject() $allocations[0]->port, ]); - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->bungeecord); // We want to make sure that the validator service runs as an admin, and not as a regular // user when saving variables. $egg->variables()->first()->update([ @@ -149,18 +156,18 @@ public function testServerIsCreatedWithDeploymentObject() */ public function testErrorEncounteredByWingsCausesServerToBeDeleted() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\Location $location */ + /** @var Location $location */ $location = Location::factory()->create(); - /** @var \Pterodactyl\Models\Node $node */ + /** @var Node $node */ $node = Node::factory()->create([ 'location_id' => $location->id, ]); - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create([ 'node_id' => $node->id, ]); @@ -178,7 +185,7 @@ public function testErrorEncounteredByWingsCausesServerToBeDeleted() 'cpu' => 0, 'startup' => 'java server2.jar', 'image' => 'java:8', - 'egg_id' => 1, + 'egg_id' => $this->bungeecord->id, 'environment' => [ 'BUNGEE_VERSION' => '123', 'SERVER_JARFILE' => 'server2.jar', @@ -200,10 +207,7 @@ public function testErrorEncounteredByWingsCausesServerToBeDeleted() $this->assertDatabaseMissing('servers', ['owner_id' => $user->id]); } - /** - * @return \Pterodactyl\Services\Servers\ServerCreationService - */ - private function getService() + private function getService(): ServerCreationService { return $this->app->make(ServerCreationService::class); } diff --git a/tests/Integration/Services/Servers/ServerDeletionServiceTest.php b/tests/Integration/Services/Servers/ServerDeletionServiceTest.php index 197e6b38f8..1a1446467e 100644 --- a/tests/Integration/Services/Servers/ServerDeletionServiceTest.php +++ b/tests/Integration/Services/Servers/ServerDeletionServiceTest.php @@ -2,11 +2,9 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; -use Mockery; -use Exception; +use Mockery\MockInterface; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; -use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use GuzzleHttp\Exception\BadResponseException; @@ -18,13 +16,11 @@ class ServerDeletionServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + private MockInterface $daemonServerRepository; - /** @var \Mockery\MockInterface */ - private $databaseManagementService; + private MockInterface $databaseManagementService; - private static $defaultLogger; + private static ?string $defaultLogger; /** * Stub out services that we don't want to test in here. @@ -37,8 +33,8 @@ public function setUp(): void // There will be some log calls during this test, don't actually write to the disk. config()->set('logging.default', 'null'); - $this->daemonServerRepository = Mockery::mock(DaemonServerRepository::class); - $this->databaseManagementService = Mockery::mock(DatabaseManagementService::class); + $this->daemonServerRepository = \Mockery::mock(DaemonServerRepository::class); + $this->databaseManagementService = \Mockery::mock(DatabaseManagementService::class); $this->app->instance(DaemonServerRepository::class, $this->daemonServerRepository); $this->app->instance(DatabaseManagementService::class, $this->databaseManagementService); @@ -102,7 +98,7 @@ public function testForceDeleteIgnoresExceptionFromWings() new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(500))) ); - $this->getService()->withForce(true)->handle($server); + $this->getService()->withForce()->handle($server); $this->assertDatabaseMissing('servers', ['id' => $server->id]); } @@ -116,17 +112,17 @@ public function testExceptionWhileDeletingStopsProcess() $server = $this->createServerModel(); $host = DatabaseHost::factory()->create(); - /** @var \Pterodactyl\Models\Database $db */ + /** @var Database $db */ $db = Database::factory()->create(['database_host_id' => $host->id, 'server_id' => $server->id]); $server->refresh(); $this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andReturnUndefined(); - $this->databaseManagementService->expects('delete')->with(Mockery::on(function ($value) use ($db) { + $this->databaseManagementService->expects('delete')->with(\Mockery::on(function ($value) use ($db) { return $value instanceof Database && $value->id === $db->id; - }))->andThrows(new Exception()); + }))->andThrows(new \Exception()); - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $this->getService()->handle($server); $this->assertDatabaseHas('servers', ['id' => $server->id]); @@ -141,15 +137,15 @@ public function testExceptionWhileDeletingDatabasesDoesNotAbortIfForceDeleted() $server = $this->createServerModel(); $host = DatabaseHost::factory()->create(); - /** @var \Pterodactyl\Models\Database $db */ + /** @var Database $db */ $db = Database::factory()->create(['database_host_id' => $host->id, 'server_id' => $server->id]); $server->refresh(); $this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andReturnUndefined(); - $this->databaseManagementService->expects('delete')->with(Mockery::on(function ($value) use ($db) { + $this->databaseManagementService->expects('delete')->with(\Mockery::on(function ($value) use ($db) { return $value instanceof Database && $value->id === $db->id; - }))->andThrows(new Exception()); + }))->andThrows(new \Exception()); $this->getService()->withForce(true)->handle($server); @@ -157,10 +153,7 @@ public function testExceptionWhileDeletingDatabasesDoesNotAbortIfForceDeleted() $this->assertDatabaseMissing('databases', ['id' => $db->id]); } - /** - * @return \Pterodactyl\Services\Servers\ServerDeletionService - */ - private function getService() + private function getService(): ServerDeletionService { return $this->app->make(ServerDeletionService::class); } diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 8f61e1a182..4f9ae90f0d 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Exception; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -23,8 +22,7 @@ class StartupModificationServiceTest extends IntegrationTestCase */ public function testNonAdminCanModifyServerVariables() { - // Theoretically lines up with the Bungeecord Minecraft egg. - $server = $this->createServerModel(['egg_id' => 1]); + $server = $this->createServerModel(); try { $this->app->make(StartupModificationService::class)->handle($server, [ @@ -35,11 +33,11 @@ public function testNonAdminCanModifyServerVariables() ], ]); - $this->assertTrue(false, 'This assertion should not be called.'); - } catch (Exception $exception) { + $this->fail('This assertion should not be called.'); + } catch (\Exception $exception) { $this->assertInstanceOf(ValidationException::class, $exception); - /** @var \Illuminate\Validation\ValidationException $exception */ + /** @var ValidationException $exception */ $errors = $exception->validator->errors()->toArray(); $this->assertCount(1, $errors); @@ -163,10 +161,7 @@ public function testInvalidEggIdTriggersException() ->handle($server, ['egg_id' => 123456789]); } - /** - * @return \Pterodactyl\Services\Servers\StartupModificationService - */ - private function getService() + private function getService(): StartupModificationService { return $this->app->make(StartupModificationService::class); } diff --git a/tests/Integration/Services/Servers/SuspensionServiceTest.php b/tests/Integration/Services/Servers/SuspensionServiceTest.php index 258a8ca2ec..e3f925cf5d 100644 --- a/tests/Integration/Services/Servers/SuspensionServiceTest.php +++ b/tests/Integration/Services/Servers/SuspensionServiceTest.php @@ -2,8 +2,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; -use Mockery; -use InvalidArgumentException; +use Mockery\MockInterface; use Pterodactyl\Models\Server; use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Tests\Integration\IntegrationTestCase; @@ -11,8 +10,7 @@ class SuspensionServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; + private MockInterface $repository; /** * Setup test instance. @@ -21,7 +19,7 @@ public function setUp(): void { parent::setUp(); - $this->repository = Mockery::mock(DaemonServerRepository::class); + $this->repository = \Mockery::mock(DaemonServerRepository::class); $this->app->instance(DaemonServerRepository::class, $this->repository); } @@ -31,7 +29,7 @@ public function testServerIsSuspendedAndUnsuspended() $this->repository->expects('setServer->sync')->twice()->andReturnSelf(); - $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->getService()->toggle($server); $this->assertTrue($server->refresh()->isSuspended()); @@ -50,7 +48,7 @@ public function testNoActionIsTakenIfSuspensionStatusIsUnchanged() $this->assertFalse($server->isSuspended()); $server->update(['status' => Server::STATUS_SUSPENDED]); - $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->getService()->toggle($server); $server->refresh(); $this->assertTrue($server->isSuspended()); @@ -60,16 +58,13 @@ public function testExceptionIsThrownIfInvalidActionsArePassed() { $server = $this->createServerModel(); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Expected one of: "suspend", "unsuspend". Got: "foo"'); $this->getService()->toggle($server, 'foo'); } - /** - * @return \Pterodactyl\Services\Servers\SuspensionService - */ - private function getService() + private function getService(): SuspensionService { return $this->app->make(SuspensionService::class); } diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 01f6f7f020..7f0e157fa1 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -11,20 +11,32 @@ class VariableValidatorServiceTest extends IntegrationTestCase { + protected Egg $egg; + + public function setUp(): void + { + parent::setUp(); + + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->egg = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + } + /** - * Test that enviornment variables for a server are validated as expected. + * Test that environment variables for a server are validated as expected. */ public function testEnvironmentVariablesCanBeValidated() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); try { $this->getService()->handle($egg->id, [ 'BUNGEE_VERSION' => '1.2.3', ]); - $this->assertTrue(false, 'This statement should not be reached.'); + $this->fail('This statement should not be reached.'); } catch (ValidationException $exception) { $errors = $exception->errors(); @@ -54,8 +66,7 @@ public function testEnvironmentVariablesCanBeValidated() */ public function testNormalUserCannotValidateNonUserEditableVariables() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -74,8 +85,7 @@ public function testNormalUserCannotValidateNonUserEditableVariables() public function testEnvironmentVariablesCanBeUpdatedAsAdmin() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -86,7 +96,7 @@ public function testEnvironmentVariablesCanBeUpdatedAsAdmin() 'SERVER_JARFILE' => 'server.jar', ]); - $this->assertTrue(false, 'This statement should not be reached.'); + $this->fail('This statement should not be reached.'); } catch (ValidationException $exception) { $this->assertCount(1, $exception->errors()); $this->assertArrayHasKey('environment.BUNGEE_VERSION', $exception->errors()); @@ -107,8 +117,7 @@ public function testEnvironmentVariablesCanBeUpdatedAsAdmin() public function testNullableEnvironmentVariablesCanBeUsedCorrectly() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->where('env_variable', '!=', 'BUNGEE_VERSION')->delete(); $egg->variables()->update(['rules' => 'nullable|string']); @@ -126,10 +135,7 @@ public function testNullableEnvironmentVariablesCanBeUsedCorrectly() $this->assertSame('', $response->get(0)->value); } - /** - * @return \Pterodactyl\Services\Servers\VariableValidatorService - */ - private function getService() + private function getService(): VariableValidatorService { return $this->app->make(VariableValidatorService::class); } diff --git a/tests/Integration/Services/Users/UserDeletionServiceTest.php b/tests/Integration/Services/Users/UserDeletionServiceTest.php new file mode 100644 index 0000000000..06564eefae --- /dev/null +++ b/tests/Integration/Services/Users/UserDeletionServiceTest.php @@ -0,0 +1,64 @@ +createServerModel(); + + $this->expectException(DisplayException::class); + $this->expectExceptionMessage(__('admin/user.exceptions.user_has_servers')); + + $this->app->make(UserDeletionService::class)->handle($server->user); + + $this->assertModelExists($server->user); + + Bus::assertNotDispatched(RevokeSftpAccessJob::class); + } + + public function testUserIsDeleted(): void + { + $user = User::factory()->create(); + + $this->app->make(UserDeletionService::class)->handle($user); + + $this->assertModelMissing($user); + + Bus::assertNotDispatched(RevokeSftpAccessJob::class); + } + + public function testUserIsDeletedAndAccessRevoked(): void + { + $user = User::factory()->create(); + + $server1 = $this->createServerModel(); + $server2 = $this->createServerModel(['node_id' => $server1->node_id]); + + Subuser::factory()->for($server1)->for($user)->create(); + Subuser::factory()->for($server2)->for($user)->create(); + + $this->app->make(UserDeletionService::class)->handle($user); + + $this->assertModelMissing($user); + + Bus::assertDispatchedTimes(RevokeSftpAccessJob::class); + Bus::assertDispatched(fn (RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server1->node)); + } +} diff --git a/tests/Integration/TestResponse.php b/tests/Integration/TestResponse.php index 9553f432a9..a5a8609c8c 100644 --- a/tests/Integration/TestResponse.php +++ b/tests/Integration/TestResponse.php @@ -12,14 +12,10 @@ class TestResponse extends IlluminateTestResponse { /** * Overrides the default assert status logic to dump out the error to the - * test output if it is caused by a 500 level error and we were not specifically + * test output if it is caused by a 500 level error, and we were not specifically * look for that status response. - * - * @param int $status - * - * @return \Pterodactyl\Tests\Integration\TestResponse */ - public function assertStatus($status) + public function assertStatus($status): TestResponse { $actual = $this->getStatusCode(); @@ -28,7 +24,11 @@ public function assertStatus($status) if ($actual !== $status && $status !== 500) { $this->dump(); if (!is_null($this->exception) && !$this->exception instanceof DisplayException && !$this->exception instanceof ValidationException) { - dump($this->exception); + dump([ + 'exception_class' => get_class($this->exception), + 'message' => $this->exception->getMessage(), + 'trace' => $this->exception->getTrace(), + ]); } } @@ -37,10 +37,7 @@ public function assertStatus($status) return $this; } - /** - * @return $this - */ - public function assertForbidden() + public function assertForbidden(): self { return self::assertStatus(Response::HTTP_FORBIDDEN); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 5e131fa8bb..9d4597e0f2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -8,8 +8,6 @@ abstract class TestCase extends BaseTestCase { - use CreatesApplication; - /** * Setup tests. */ @@ -17,8 +15,10 @@ public function setUp(): void { parent::setUp(); - Carbon::setTestNow(Carbon::now()); - CarbonImmutable::setTestNow(Carbon::now()); + $now = Carbon::now()->startOfSecond(); + + Carbon::setTestNow($now); + CarbonImmutable::setTestNow($now); // Why, you ask? If we don't force this to false it is possible for certain exceptions // to show their error message properly in the integration test output, but not actually diff --git a/tests/Traits/Http/IntegrationJsonRequestAssertions.php b/tests/Traits/Http/IntegrationJsonRequestAssertions.php index e26235a929..4583d53122 100644 --- a/tests/Traits/Http/IntegrationJsonRequestAssertions.php +++ b/tests/Traits/Http/IntegrationJsonRequestAssertions.php @@ -10,7 +10,7 @@ trait IntegrationJsonRequestAssertions /** * Make assertions about a 404 response on the API. */ - public function assertNotFoundJson(TestResponse $response) + public function assertNotFoundJson(TestResponse $response): void { $response->assertStatus(Response::HTTP_NOT_FOUND); $response->assertJsonStructure(['errors' => [['code', 'status', 'detail']]]); @@ -20,7 +20,7 @@ public function assertNotFoundJson(TestResponse $response) [ 'code' => 'NotFoundHttpException', 'status' => '404', - 'detail' => 'The requested resource does not exist on this server.', + 'detail' => 'The requested resource could not be found on the server.', ], ], ], true); @@ -29,7 +29,7 @@ public function assertNotFoundJson(TestResponse $response) /** * Make assertions about a 403 error returned by the API. */ - public function assertAccessDeniedJson(TestResponse $response) + public function assertAccessDeniedJson(TestResponse $response): void { $response->assertStatus(Response::HTTP_FORBIDDEN); $response->assertJsonStructure(['errors' => [['code', 'status', 'detail']]]); diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index aebb61ce95..9cbe315bd3 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -4,7 +4,6 @@ use Closure; use Illuminate\Http\Request; -use BadFunctionCallException; trait MocksMiddlewareClosure { @@ -12,10 +11,10 @@ trait MocksMiddlewareClosure * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. */ - protected function getClosureAssertions(): Closure + protected function getClosureAssertions(): \Closure { if (is_null($this->request)) { - throw new BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); + throw new \BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); } return function ($response) { diff --git a/tests/Traits/Http/RequestMockHelpers.php b/tests/Traits/Http/RequestMockHelpers.php index 668d809e65..54cc0bd078 100644 --- a/tests/Traits/Http/RequestMockHelpers.php +++ b/tests/Traits/Http/RequestMockHelpers.php @@ -3,27 +3,21 @@ namespace Pterodactyl\Tests\Traits\Http; use Mockery as m; +use Mockery\Mock; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use InvalidArgumentException; use Symfony\Component\HttpFoundation\ParameterBag; trait RequestMockHelpers { - /** - * @var string - */ - private $requestMockClass = Request::class; + private string $requestMockClass = Request::class; - /** - * @var \Illuminate\Http\Request|\Mockery\Mock - */ - protected $request; + protected Request|Mock $request; /** * Set the class to mock for requests. */ - public function setRequestMockClass(string $class) + public function setRequestMockClass(string $class): void { $this->requestMockClass = $class; @@ -33,7 +27,7 @@ public function setRequestMockClass(string $class) /** * Configure the user model that the request mock should return with. */ - public function setRequestUserModel(User $user = null) + public function setRequestUserModel(?User $user = null): void { $this->request->shouldReceive('user')->andReturn($user); } @@ -43,6 +37,7 @@ public function setRequestUserModel(User $user = null) */ public function generateRequestUserModel(array $args = []): User { + /** @var User $user */ $user = User::factory()->make($args); $this->setRequestUserModel($user); @@ -51,10 +46,8 @@ public function generateRequestUserModel(array $args = []): User /** * Set a request attribute on the mock object. - * - * @param mixed $value */ - public function setRequestAttribute(string $attribute, $value) + public function setRequestAttribute(string $attribute, mixed $value): void { $this->request->attributes->set($attribute, $value); } @@ -62,7 +55,7 @@ public function setRequestAttribute(string $attribute, $value) /** * Set the request route name. */ - public function setRequestRouteName(string $name) + public function setRequestRouteName(string $name): void { $this->request->shouldReceive('route->getName')->andReturn($name); } @@ -70,11 +63,11 @@ public function setRequestRouteName(string $name) /** * Set the active request object to be an instance of a mocked request. */ - protected function buildRequestMock() + protected function buildRequestMock(): void { $this->request = m::mock($this->requestMockClass); if (!$this->request instanceof Request) { - throw new InvalidArgumentException('Request mock class must be an instance of ' . Request::class . ' when mocked.'); + throw new \InvalidArgumentException('Request mock class must be an instance of ' . Request::class . ' when mocked.'); } $this->request->attributes = new ParameterBag(); @@ -86,7 +79,7 @@ protected function buildRequestMock() * * @deprecated */ - protected function setRequestUser(User $user = null): User + protected function setRequestUser(?User $user = null): User { $user = $user instanceof User ? $user : User::factory()->make(); $this->request->shouldReceive('user')->withNoArgs()->andReturn($user); diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index bafa9baa88..a90de9f6cb 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -4,10 +4,10 @@ use Ramsey\Uuid\Uuid; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Location; use Pterodactyl\Models\Allocation; @@ -18,59 +18,54 @@ trait CreatesTestModels * is passed in that normally requires this function to create a model no model will be * created and that attribute's value will be used. * - * The returned server model will have all of the relationships loaded onto it. - * - * @return \Pterodactyl\Models\Server + * The returned server model will have all the relationships loaded onto it. */ - public function createServerModel(array $attributes = []) + public function createServerModel(array $attributes = []): Server { if (isset($attributes['user_id'])) { $attributes['owner_id'] = $attributes['user_id']; } if (!isset($attributes['owner_id'])) { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $attributes['owner_id'] = $user->id; } if (!isset($attributes['node_id'])) { if (!isset($attributes['location_id'])) { - /** @var \Pterodactyl\Models\Location $location */ + /** @var Location $location */ $location = Location::factory()->create(); $attributes['location_id'] = $location->id; } - /** @var \Pterodactyl\Models\Node $node */ + /** @var Node $node */ $node = Node::factory()->create(['location_id' => $attributes['location_id']]); $attributes['node_id'] = $node->id; } if (!isset($attributes['allocation_id'])) { - /** @var \Pterodactyl\Models\Allocation $allocation */ + /** @var Allocation $allocation */ $allocation = Allocation::factory()->create(['node_id' => $attributes['node_id']]); $attributes['allocation_id'] = $allocation->id; } - if (!isset($attributes['nest_id'])) { - /** @var \Pterodactyl\Models\Nest $nest */ - $nest = Nest::with('eggs')->first(); - $attributes['nest_id'] = $nest->id; + if (empty($attributes['egg_id'])) { + $egg = !empty($attributes['nest_id']) + ? Egg::query()->where('nest_id', $attributes['nest_id'])->firstOrFail() + : $this->getBungeecordEgg(); - if (!isset($attributes['egg_id'])) { - $attributes['egg_id'] = $nest->getRelation('eggs')->first()->id; - } + $attributes['egg_id'] = $egg->id; + $attributes['nest_id'] = $egg->nest_id; } - if (!isset($attributes['egg_id'])) { - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = Egg::where('nest_id', $attributes['nest_id'])->first(); - $attributes['egg_id'] = $egg->id; + if (empty($attributes['nest_id'])) { + $attributes['nest_id'] = Egg::query()->findOrFail($attributes['egg_id'])->nest_id; } unset($attributes['user_id'], $attributes['location_id']); - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ $server = Server::factory()->create($attributes); Allocation::query()->where('id', $server->allocation_id)->update(['server_id' => $server->id]); @@ -80,6 +75,34 @@ public function createServerModel(array $attributes = []) ]); } + /** + * Generates a user and a server for that user. If an array of permissions is passed it + * is assumed that the user is actually a subuser of the server. + * + * @param string[] $permissions + * + * @return array{\Pterodactyl\Models\User, \Pterodactyl\Models\Server} + */ + public function generateTestAccount(array $permissions = []): array + { + /** @var User $user */ + $user = User::factory()->create(); + + if (empty($permissions)) { + return [$user, $this->createServerModel(['user_id' => $user->id])]; + } + + $server = $this->createServerModel(); + + Subuser::query()->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => $permissions, + ]); + + return [$user, $server]; + } + /** * Clones a given egg allowing us to make modifications that don't affect other * tests that rely on the egg existing in the correct state. @@ -90,7 +113,7 @@ protected function cloneEggAndVariables(Egg $egg): Egg $model->uuid = Uuid::uuid4()->toString(); $model->push(); - /** @var \Pterodactyl\Models\Egg $model */ + /** @var Egg $model */ $model = $model->fresh(); foreach ($egg->variables as $variable) { @@ -99,4 +122,16 @@ protected function cloneEggAndVariables(Egg $egg): Egg return $model->fresh(); } + + /** + * Almost every test just assumes it is using BungeeCord — this is the critical + * egg model for all tests unless specified otherwise. + */ + private function getBungeecordEgg(): Egg + { + /** @var Egg $egg */ + $egg = Egg::query()->where('author', 'support@pterodactyl.io')->where('name', 'Bungeecord')->firstOrFail(); + + return $egg; + } } diff --git a/tests/Traits/MocksPdoConnection.php b/tests/Traits/MocksPdoConnection.php index c7fe8cdd0a..527fac62f7 100644 --- a/tests/Traits/MocksPdoConnection.php +++ b/tests/Traits/MocksPdoConnection.php @@ -7,27 +7,23 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\MySqlConnection; use Illuminate\Database\ConnectionResolver; +use Illuminate\Database\ConnectionResolverInterface; trait MocksPdoConnection { - /** - * @var \Illuminate\Database\ConnectionResolverInterface|null - */ - private static $initialResolver; + private static ?ConnectionResolverInterface $initialResolver; /** * Generates a mock PDO connection and injects it into the models so that any actual * DB call can be properly intercepted. - * - * @return \Mockery\MockInterface */ - protected function mockPdoConnection() + protected function mockPdoConnection(): Mockery\MockInterface { self::$initialResolver = Model::getConnectionResolver(); Model::unsetConnectionResolver(); - $connection = new MySqlConnection($mock = Mockery::mock(PDO::class), 'testing_mock'); + $connection = new MySqlConnection($mock = \Mockery::mock(\PDO::class), 'testing_mock'); $resolver = new ConnectionResolver(['mocked' => $connection]); $resolver->setDefaultConnection('mocked'); @@ -39,7 +35,7 @@ protected function mockPdoConnection() /** * Resets the mock state. */ - protected function tearDownPdoMock() + protected function tearDownPdoMock(): void { if (!self::$initialResolver) { return; diff --git a/tests/Traits/MocksRequestException.php b/tests/Traits/MocksRequestException.php index 69c04913ad..16078707d7 100644 --- a/tests/Traits/MocksRequestException.php +++ b/tests/Traits/MocksRequestException.php @@ -2,29 +2,21 @@ namespace Pterodactyl\Tests\Traits; -use Mockery; +use Mockery\Mock; use Mockery\MockInterface; use GuzzleHttp\Exception\RequestException; trait MocksRequestException { - /** - * @var \GuzzleHttp\Exception\RequestException|\Mockery\Mock - */ - private $exception; + private RequestException|Mock $exception; - /** - * @var mixed - */ - private $exceptionResponse; + private mixed $exceptionResponse; /** * Configure the exception mock to work with the Panel's default exception * handler actions. - * - * @param null $response */ - protected function configureExceptionMock(string $abstract = RequestException::class, $response = null) + protected function configureExceptionMock(string $abstract = RequestException::class, $response = null): void { $this->getExceptionMock($abstract)->shouldReceive('getResponse')->andReturn(value($response)); } @@ -34,6 +26,6 @@ protected function configureExceptionMock(string $abstract = RequestException::c */ protected function getExceptionMock(string $abstract = RequestException::class): MockInterface { - return $this->exception ?? $this->exception = Mockery::mock($abstract); + return $this->exception ?? $this->exception = \Mockery::mock($abstract); } } diff --git a/tests/Traits/MocksUuids.php b/tests/Traits/MocksUuids.php index 0a84fce126..fb5b7c9715 100644 --- a/tests/Traits/MocksUuids.php +++ b/tests/Traits/MocksUuids.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Tests\Traits; @@ -17,15 +10,13 @@ trait MocksUuids { /** * The known UUID string. - * - * @var string */ - protected $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; + protected string $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; /** * Setup a factory mock to produce the same UUID whenever called. */ - public function setKnownUuidFactory() + public function setKnownUuidFactory(): void { $uuid = Uuid::fromString($this->getKnownUuid()); $factoryMock = m::mock(UuidFactory::class . '[uuid4]', [ diff --git a/tests/Unit/Helpers/EnvironmentWriterTraitTest.php b/tests/Unit/Helpers/EnvironmentWriterTraitTest.php index f2022f7982..588bb8e980 100644 --- a/tests/Unit/Helpers/EnvironmentWriterTraitTest.php +++ b/tests/Unit/Helpers/EnvironmentWriterTraitTest.php @@ -7,9 +7,7 @@ class EnvironmentWriterTraitTest extends TestCase { - /** - * @dataProvider variableDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('variableDataProvider')] public function testVariableIsEscapedProperly($input, $expected) { $output = (new FooClass())->escapeEnvironmentValue($input); @@ -17,7 +15,7 @@ public function testVariableIsEscapedProperly($input, $expected) $this->assertSame($expected, $output); } - public function variableDataProvider(): array + public static function variableDataProvider(): array { return [ ['foo', 'foo'], diff --git a/tests/Unit/Helpers/IsDigitTest.php b/tests/Unit/Helpers/IsDigitTest.php index eae5fbac5a..273de64aee 100644 --- a/tests/Unit/Helpers/IsDigitTest.php +++ b/tests/Unit/Helpers/IsDigitTest.php @@ -8,9 +8,8 @@ class IsDigitTest extends TestCase { /** * Test the is_digit helper. - * - * @dataProvider helperDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('helperDataProvider')] public function testHelper($value, $response) { $this->assertSame($response, is_digit($value)); @@ -18,10 +17,8 @@ public function testHelper($value, $response) /** * Provide data to test against the helper function. - * - * @return array */ - public function helperDataProvider() + public static function helperDataProvider(): array { return [ [true, false], diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php index 8ba4c75fc9..429d63084e 100644 --- a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php +++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php @@ -21,7 +21,7 @@ public function testNoUserDefined() } /** - * Test that a non-admin user results an an exception. + * Test that a non-admin user results in an exception. */ public function testNonAdminUser() { diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php deleted file mode 100644 index c362e9cca7..0000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php +++ /dev/null @@ -1,73 +0,0 @@ -make(['allowed_ips' => []]); - $this->setRequestAttribute('api_key', $model); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test middleware works correctly when a valid IP accesses - * and there is an IP restriction. - */ - public function testWithValidIP() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('127.0.0.1'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a CIDR range can be used. - */ - public function testValidIPAgainstCIDRRange() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['192.168.1.1/28']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('192.168.1.15'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that an exception is thrown when an invalid IP address - * tries to connect and there is an IP restriction. - */ - public function testWithInvalidIP() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->twice()->andReturn('127.0.0.2'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware to be used when testing. - */ - private function getMiddleware(): AuthenticateIPAccess - { - return new AuthenticateIPAccess(); - } -} diff --git a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php index 945e08701b..f3ff026c3f 100644 --- a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php +++ b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware\Api\Daemon; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\Node; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -15,15 +16,9 @@ class DaemonAuthenticateTest extends MiddlewareTestCase { - /** - * @var \Mockery\MockInterface - */ - private $repository; + private MockInterface $encrypter; - /** - * @var \Mockery\MockInterface - */ - private $encrypter; + private MockInterface $repository; /** * Setup tests. @@ -69,9 +64,8 @@ public function testResponseShouldFailIfNoTokenIsProvided() /** * Test that passing in an invalid node daemon secret will result in a bad request * exception being returned. - * - * @dataProvider badTokenDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('badTokenDataProvider')] public function testResponseShouldFailIfTokenFormatIsIncorrect(string $token) { $this->expectException(BadRequestHttpException::class); @@ -90,7 +84,7 @@ public function testResponseShouldFailIfTokenIsNotValid() { $this->expectException(AccessDeniedHttpException::class); - /** @var \Pterodactyl\Models\Node $model */ + /** @var Node $model */ $model = Node::factory()->make(); $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route'); @@ -123,7 +117,7 @@ public function testResponseShouldFailIfNodeIsNotFound() */ public function testSuccessfulMiddlewareProcess() { - /** @var \Pterodactyl\Models\Node $model */ + /** @var Node $model */ $model = Node::factory()->make(); $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route'); @@ -143,7 +137,7 @@ public function testSuccessfulMiddlewareProcess() * * @return array|\string[][] */ - public function badTokenDataProvider(): array + public static function badTokenDataProvider(): array { return [ ['foo'], diff --git a/tests/Unit/Http/Middleware/AuthenticateTest.php b/tests/Unit/Http/Middleware/AuthenticateTest.php deleted file mode 100644 index 828afc1222..0000000000 --- a/tests/Unit/Http/Middleware/AuthenticateTest.php +++ /dev/null @@ -1,39 +0,0 @@ -request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a logged out user results in an exception. - */ - public function testLoggedOutUser() - { - $this->expectException(AuthenticationException::class); - - $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware using mocked dependencies. - */ - private function getMiddleware(): Authenticate - { - return new Authenticate(); - } -} diff --git a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php index 569ef417a2..c870d8e5b4 100644 --- a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php +++ b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php @@ -3,16 +3,14 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\User; use Illuminate\Foundation\Application; use Pterodactyl\Http\Middleware\LanguageMiddleware; class LanguageMiddlewareTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Foundation\Application|\Mockery\Mock - */ - private $appMock; + private MockInterface $appMock; /** * Setup tests. diff --git a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php index a007dd34ad..573c788140 100644 --- a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php +++ b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Server; @@ -11,10 +12,7 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory|\Mockery\Mock - */ - private $response; + private MockInterface $response; /** * Setup tests. diff --git a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php index ea4bc8014a..68f238aaf9 100644 --- a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php +++ b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php @@ -3,16 +3,14 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Illuminate\Auth\AuthManager; use Illuminate\Http\RedirectResponse; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; class RedirectIfAuthenticatedTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Auth\AuthManager|\Mockery\Mock - */ - private $authManager; + private MockInterface $authManager; /** * Setup tests. diff --git a/tests/Unit/Rules/UsernameTest.php b/tests/Unit/Rules/UsernameTest.php index 829cab2c32..f01589e959 100644 --- a/tests/Unit/Rules/UsernameTest.php +++ b/tests/Unit/Rules/UsernameTest.php @@ -17,9 +17,8 @@ public function testRuleIsStringable() /** * Test valid usernames. - * - * @dataProvider validUsernameDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('validUsernameDataProvider')] public function testValidUsernames(string $username) { $this->assertTrue((new Username())->passes('test', $username), 'Assert username is valid.'); @@ -27,9 +26,8 @@ public function testValidUsernames(string $username) /** * Test invalid usernames return false. - * - * @dataProvider invalidUsernameDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('invalidUsernameDataProvider')] public function testInvalidUsernames(string $username) { $this->assertFalse((new Username())->passes('test', $username), 'Assert username is not valid.'); @@ -38,7 +36,7 @@ public function testInvalidUsernames(string $username) /** * Provide valid usernames. */ - public function validUsernameDataProvider(): array + public static function validUsernameDataProvider(): array { return [ ['username'], @@ -54,7 +52,7 @@ public function validUsernameDataProvider(): array /** * Provide invalid usernames. */ - public function invalidUsernameDataProvider(): array + public static function invalidUsernameDataProvider(): array { return [ ['_username'], diff --git a/tests/Unit/Services/Acl/Api/AdminAclTest.php b/tests/Unit/Services/Acl/Api/AdminAclTest.php index daaea838f1..b135bd92a0 100644 --- a/tests/Unit/Services/Acl/Api/AdminAclTest.php +++ b/tests/Unit/Services/Acl/Api/AdminAclTest.php @@ -10,9 +10,8 @@ class AdminAclTest extends TestCase { /** * Test that permissions return the expects values. - * - * @dataProvider permissionsDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsDataProvider')] public function testPermissions(int $permission, int $check, bool $outcome) { $this->assertSame($outcome, AdminAcl::can($permission, $check)); @@ -31,7 +30,7 @@ public function testCheck() /** * Provide valid and invalid permissions combos for testing. */ - public function permissionsDataProvider(): array + public static function permissionsDataProvider(): array { return [ [AdminAcl::READ, AdminAcl::READ, true], diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php deleted file mode 100644 index 5c847f1448..0000000000 --- a/tests/Unit/Services/Api/KeyCreationServiceTest.php +++ /dev/null @@ -1,167 +0,0 @@ -encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(ApiKeyRepositoryInterface::class); - } - - /** - * Test that the service is able to create a keypair and assign the correct permissions. - */ - public function testKeyIsCreated() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'test-data' => 'test', - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['test-data' => 'test']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that an identifier is only set by the function. - */ - public function testIdentifierAndTokenAreOnlySetByFunction() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['identifier' => 'customIdentifier', 'token' => 'customToken']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions passed in are loaded onto the key data. - */ - public function testPermissionsAreRetrievedForApplicationKeys() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - 'permission-key' => 'exists', - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType(ApiKey::TYPE_APPLICATION)->handle([], ['permission-key' => 'exists']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions are not retrieved for any key that is not an application key. - * - * @dataProvider keyTypeDataProvider - */ - public function testPermissionsAreNotRetrievedForNonApplicationKeys($keyType) - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => $keyType, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType($keyType)->handle([], ['fake-permission' => 'should-not-exist']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Provide key types that are not an application specific key. - */ - public function keyTypeDataProvider(): array - { - return [ - [ApiKey::TYPE_NONE], [ApiKey::TYPE_ACCOUNT], [ApiKey::TYPE_DAEMON_USER], [ApiKey::TYPE_DAEMON_APPLICATION], - ]; - } - - /** - * Return an instance of the service with mocked dependencies for testing. - */ - private function getService(): KeyCreationService - { - return new KeyCreationService($this->repository, $this->encrypter); - } -} diff --git a/tsconfig.json b/tsconfig.json index 26423ecd66..a425fd922f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2015", "module": "es2020", - "jsx": "react", + "jsx": "react-jsx", "moduleResolution": "node", "lib": [ "es2015", @@ -17,11 +17,14 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", - "importsNotUsedAsValues": "preserve", + "verbatimModuleSyntax": false, "paths": { "@/*": [ "./resources/scripts/*" ], + "@definitions/*": [ + "./resources/scripts/api/definitions/*" + ], "@feature/*": [ "./resources/scripts/components/server/features/*" ] diff --git a/webpack.config.js b/webpack.config.js index 777b0be1b3..af808697e0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,37 +1,62 @@ -const path = require('path'); -const AssetsManifestPlugin = require('webpack-assets-manifest'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const path = require('node:path'); +const webpack = require('webpack'); +const { WebpackAssetsManifest } = require('webpack-assets-manifest'); const TerserPlugin = require('terser-webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const isProduction = process.env.NODE_ENV === 'production'; module.exports = { cache: true, target: 'web', - mode: process.env.NODE_ENV, - devtool: isProduction ? false : (process.env.DEVTOOL || 'eval-source-map'), + mode: isProduction ? 'production' : 'development', + devtool: process.env.DEVTOOL || (isProduction ? false : 'eval-source-map'), performance: { hints: false, }, entry: ['react-hot-loader/patch', './resources/scripts/index.tsx'], output: { path: path.join(__dirname, '/public/assets'), - filename: isProduction ? 'bundle.[chunkhash:8].js' : 'bundle.[hash:8].js', - chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].[hash:8].js', - publicPath: (process.env.WEBPACK_PUBLIC_PATH || '/assets/'), + filename: isProduction ? 'bundle.[chunkhash:8].js' : 'bundle.[fullhash:8].js', + chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].[fullhash:8].js', + publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/', crossOriginLoading: 'anonymous', }, module: { rules: [ { test: /\.tsx?$/, - exclude: /node_modules/, + exclude: /node_modules|\.spec\.tsx?$/, loader: 'babel-loader', }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, { test: /\.css$/, - use: [ 'style-loader', 'css-loader' ], + use: [ + { loader: 'style-loader' }, + { + loader: 'css-loader', + options: { + modules: { + auto: true, + // https://github.com/webpack/css-loader/blob/main/CHANGELOG.md#700-2024-04-04 + namedExport: false, + exportLocalsConvention: 'as-is', + localIdentName: isProduction ? '[name]_[hash:base64:8]' : '[path][name]__[local]', + localIdentContext: path.join(__dirname, 'resources/scripts/components'), + }, + sourceMap: !isProduction, + importLoaders: 1, + }, + }, + { + loader: 'postcss-loader', + options: { sourceMap: !isProduction }, + }, + ], }, { test: /\.(png|jp(e?)g|gif)$/, @@ -40,6 +65,10 @@ module.exports = { name: 'images/[name].[hash:8].[ext]', }, }, + { + test: /\.(woff|woff2)$/i, + type: 'asset/resource', + }, { test: /\.svg$/, loader: 'svg-url-loader', @@ -48,7 +77,7 @@ module.exports = { test: /\.js$/, enforce: 'pre', loader: 'source-map-loader', - } + }, ], }, stats: { @@ -60,6 +89,7 @@ module.exports = { extensions: ['.ts', '.tsx', '.js', '.json'], alias: { '@': path.join(__dirname, '/resources/scripts'), + '@definitions': path.join(__dirname, '/resources/scripts/api/definitions'), '@feature': path.join(__dirname, '/resources/scripts/components/server/features'), }, symlinks: false, @@ -70,24 +100,19 @@ module.exports = { moment: 'moment', }, plugins: [ - new AssetsManifestPlugin({ writeToDisk: true, publicPath: true, integrity: true, integrityHashes: ['sha384'] }), - new ForkTsCheckerWebpackPlugin({ - typescript: { - mode: 'write-references', - diagnosticOptions: { - semantic: true, - syntactic: true, - }, - }, - eslint: isProduction ? undefined : { - files: `${path.join(__dirname, '/resources/scripts')}/**/*.{ts,tsx}`, - } + new webpack.EnvironmentPlugin({ + NODE_ENV: process.env.NODE_ENV || 'development', + DEBUG: process.env.NODE_ENV !== 'production', + WEBPACK_BUILD_HASH: Date.now().toString(16), }), - process.env.ANALYZE_BUNDLE ? new BundleAnalyzerPlugin({ - analyzerHost: '0.0.0.0', - analyzerPort: 8081, - }) : null - ].filter(p => p), + new WebpackAssetsManifest({ + output: 'manifest.json', + writeToDisk: true, + publicPath: true, + integrity: true, + integrityHashes: ['sha384'], + }), + ], optimization: { usedExports: true, sideEffects: false, @@ -96,7 +121,6 @@ module.exports = { minimize: isProduction, minimizer: [ new TerserPlugin({ - cache: isProduction, parallel: true, extractComments: false, terserOptions: { @@ -114,11 +138,22 @@ module.exports = { }, devServer: { compress: true, - contentBase: path.join(__dirname, '/public'), - publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/', - allowedHosts: [ - '.pterodactyl.test', - ], + port: 5173, + server: { + type: 'https', + options: process.env.USE_LOCAL_CERTS + ? { + ca: path.join(__dirname, '../../docker/certificates/root_ca.pem'), + cert: path.join(__dirname, '../../docker/certificates/pterodactyl.test.pem'), + key: path.join(__dirname, '../../docker/certificates/pterodactyl.test-key.pem'), + } + : undefined, + }, + static: { + directory: path.join(__dirname, '/public'), + publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/', + }, + allowedHosts: ['.pterodactyl.test'], headers: { 'Access-Control-Allow-Origin': '*', }, diff --git a/yarn.lock b/yarn.lock index 9c67ff2468..e857894035 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,84 +2,111 @@ # yarn lockfile v1 -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@ampproject/remapping@^2.1.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== dependencies: - "@babel/highlight" "^7.10.4" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== +"@babel/code-frame@^7.10.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: - "@babel/highlight" "^7.10.4" + "@babel/highlight" "^7.16.7" "@babel/compat-data@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.1.tgz#d7386a689aa0ddf06255005b4b991988021101a0" - integrity sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ== + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" + integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== + +"@babel/compat-data@^7.17.10", "@babel/compat-data@^7.27.2": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" + integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" + integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" "@babel/core@^7.12.1": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" - integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.1" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.1" - "@babel/parser" "^7.12.3" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" + integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.5" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.5" + "@babel/types" "^7.18.4" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.4.tgz#e49eeed9fe114b62fa5b181856a43a5e32f5f243" - integrity sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng== - dependencies: - "@babel/types" "^7.10.4" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" -"@babel/generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468" - integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg== +"@babel/generator@^7.18.2", "@babel/generator@^7.28.5", "@babel/generator@^7.7.2": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== dependencies: - "@babel/types" "^7.12.1" - jsesc "^2.5.1" - source-map "^0.5.0" + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.0.0": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz#bb3faf1e74b74bd547e867e48f551fa6b098b6ce" +"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: - "@babel/types" "^7.7.4" + "@babel/types" "^7.16.7" -"@babel/helper-annotate-as-pure@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" - integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.27.3" "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" @@ -107,14 +134,25 @@ "@babel/types" "^7.10.4" "@babel/helper-compilation-targets@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz#310e352888fbdbdd8577be8dfdd2afb9e7adcf50" - integrity sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: - "@babel/compat-data" "^7.12.1" - "@babel/helper-validator-option" "^7.12.1" - browserslist "^4.12.0" - semver "^5.5.0" + "@babel/compat-data" "^7.17.10" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" "@babel/helper-create-class-features-plugin@^7.12.1": version "7.12.1" @@ -145,6 +183,13 @@ "@babel/types" "^7.10.5" lodash "^4.17.19" +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-explode-assignable-expression@^7.10.4": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633" @@ -153,27 +198,39 @@ "@babel/types" "^7.12.1" "@babel/helper-function-name@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" - integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== dependencies: - "@babel/helper-get-function-arity" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" -"@babel/helper-get-function-arity@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" - integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== +"@babel/helper-function-name@^7.17.9": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== dependencies: - "@babel/types" "^7.10.4" + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== "@babel/helper-hoist-variables@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" - integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" "@babel/helper-member-expression-to-functions@^7.12.1": version "7.12.1" @@ -182,33 +239,43 @@ dependencies: "@babel/types" "^7.12.1" -"@babel/helper-module-imports@^7.0.0": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91" +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: - "@babel/types" "^7.7.4" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" -"@babel/helper-module-imports@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz#1644c01591a15a2f084dd6d092d9430eb1d1216c" - integrity sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA== +"@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.16.7" "@babel/helper-module-transforms@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" - integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w== - dependencies: - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-replace-supers" "^7.12.1" - "@babel/helper-simple-access" "^7.12.1" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/helper-validator-identifier" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" - lodash "^4.17.19" + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" + +"@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" @@ -217,18 +284,15 @@ dependencies: "@babel/types" "^7.10.4" -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - -"@babel/helper-plugin-utils@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== -"@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" +"@babel/helper-plugin-utils@^7.8.3": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== "@babel/helper-regex@^7.10.4": version "7.10.5" @@ -256,12 +320,20 @@ "@babel/traverse" "^7.12.1" "@babel/types" "^7.12.1" -"@babel/helper-simple-access@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136" - integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA== +"@babel/helper-simple-access@^7.17.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.27.1.tgz#7f2171e29d95d6283ede286a4994f985cbe07973" + integrity sha512-OU4zVQrJgFBNXMjrHs1yFSdlTgufO4tefcUZoqNhukVfw0p8x1Asht/gcGZ3bpHbi8gu/76m4JhrlKPqkrs/WQ== dependencies: - "@babel/types" "^7.12.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-simple-access@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== + dependencies: + "@babel/types" "^7.18.2" "@babel/helper-skip-transparent-expression-wrappers@^7.12.1": version "7.12.1" @@ -271,28 +343,43 @@ "@babel/types" "^7.12.1" "@babel/helper-split-export-declaration@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" - integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.16.7" -"@babel/helper-split-export-declaration@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== +"@babel/helper-split-export-declaration@^7.16.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== dependencies: - "@babel/types" "^7.11.0" + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== "@babel/helper-validator-identifier@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" - integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== "@babel/helper-validator-option@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9" - integrity sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== "@babel/helper-wrap-function@^7.10.4": version "7.12.3" @@ -304,46 +391,30 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helpers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.1.tgz#8a8261c1d438ec18cb890434df4ec768734c1e79" - integrity sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g== +"@babel/helpers@^7.18.2", "@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== dependencies: - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" -"@babel/highlight@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" +"@babel/highlight@^7.16.7": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" + integrity sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw== dependencies: - chalk "^2.0.0" - esutils "^2.0.2" + "@babel/helper-validator-identifier" "^7.25.9" + chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/highlight@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" - integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64" - integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== - -"@babel/parser@^7.12.1", "@babel/parser@^7.12.3": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" - integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== - -"@babel/parser@^7.12.5": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" - integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== + "@babel/types" "^7.28.5" "@babel/plugin-proposal-async-generator-functions@^7.12.1": version "7.12.1" @@ -452,19 +523,33 @@ "@babel/helper-create-regexp-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-async-generators@^7.8.0": +"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978" - integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA== +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.1", "@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" @@ -480,7 +565,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-json-strings@^7.8.0": +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== @@ -494,6 +593,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-jsx@^7.22.5": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -501,7 +607,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== @@ -515,39 +621,53 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.8.0": +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": +"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-chaining@^7.8.0": +"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" - integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz#460ba9d77077653803c3dd2e673f76d66b4029e5" - integrity sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.17.12" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-arrow-functions@^7.12.1": version "7.12.1" @@ -668,14 +788,14 @@ "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz#fa403124542636c786cf9b460a0ffbb48a86e648" - integrity sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag== +"@babel/plugin-transform-modules-commonjs@^7.12.1", "@babel/plugin-transform-modules-commonjs@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e" + integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ== dependencies: - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-simple-access" "^7.12.1" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-simple-access" "^7.18.2" babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-systemjs@^7.12.1": @@ -970,236 +1090,1432 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-typescript" "^7.12.1" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3": - version "7.7.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.5.tgz#4b087f183f5d83647744d4157f66199081d17a00" - dependencies: - regenerator-runtime "^0.13.2" - -"@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.4.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.7.2", "@babel/runtime@^7.9.6": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" - integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw== - dependencies: - regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.5", "@babel/runtime@^7.7.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + +"@babel/runtime@^7.28.4": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== "@babel/template@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e" - integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw== +"@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.24.7", "@babel/template@^7.27.2", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.1" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.12.1" - "@babel/types" "^7.12.1" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.19" + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" -"@babel/traverse@^7.4.5": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.4.tgz#e642e5395a3b09cc95c8e74a27432b484b697818" - integrity sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.10.4" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" +"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" + integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.18.5" + "@babel/types" "^7.18.4" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" -"@babel/types@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee" - integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.13" +"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.5", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.5" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.3.3": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.12.1", "@babel/types@^7.4.4": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.4.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae" - integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@babel/types@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.4.tgz#516570d539e44ddf308c07569c258ff94fde9193" - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" +"@csstools/cascade-layer-name-parser@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz#43f962bebead0052a9fed1a2deeb11f85efcbc72" + integrity sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A== -"@emotion/is-prop-valid@^0.8.8": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" - integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== + +"@csstools/css-calc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" + integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + +"@csstools/css-color-parser@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== dependencies: - "@emotion/memoize" "0.7.4" + "@csstools/color-helpers" "^5.1.0" + "@csstools/css-calc" "^2.1.4" -"@emotion/memoize@0.7.4": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" - integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@csstools/css-parser-algorithms@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== -"@emotion/stylis@^0.8.4": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== +"@csstools/css-tokenizer@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== -"@emotion/unitless@^0.7.4": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@csstools/media-query-list-parser@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz#7aec77bcb89c2da80ef207e73f474ef9e1b3cdf1" + integrity sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== -"@eslint/eslintrc@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" - integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ== +"@csstools/postcss-alpha-function@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz#7989605711de7831bc7cd75b94c9b5bac9c3728e" + integrity sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w== dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -"@fortawesome/fontawesome-common-types@^0.2.32": - version "0.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz#3436795d5684f22742989bfa08f46f50f516f259" - integrity sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w== +"@csstools/postcss-cascade-layers@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz#dd2c70db3867b88975f2922da3bfbae7d7a2cae7" + integrity sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" -"@fortawesome/fontawesome-svg-core@^1.2.32": - version "1.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz#da092bfc7266aa274be8604de610d7115f9ba6cf" - integrity sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ== +"@csstools/postcss-color-function-display-p3-linear@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz#3017ff5e1f65307d6083e58e93d76724fb1ebf9f" + integrity sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz#a7c85a98c77b522a194a1bbb00dd207f40c7a771" + integrity sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-mix-function@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz#2f1ee9f8208077af069545c9bd79bb9733382c2a" + integrity sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-mix-variadic-function-arguments@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz#b4012b62a4eaa24d694172bb7137f9d2319cb8f2" + integrity sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-content-alt-text@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz#1d52da1762893c32999ff76839e48d6ec7c7a4cb" + integrity sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-contrast-color-function@^2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz#ca46986d095c60f208d9e3f24704d199c9172637" + integrity sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-exponential-functions@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz#fc03d1272888cb77e64cc1a7d8a33016e4f05c69" + integrity sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-font-format-keywords@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz#6730836eb0153ff4f3840416cc2322f129c086e6" + integrity sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-gamut-mapping@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz#be0e34c9f0142852cccfc02b917511f0d677db8b" + integrity sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-gradients-interpolation-method@^5.0.12": + version "5.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz#0955cce4d97203b861bf66742bbec611b2f3661c" + integrity sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-hwb-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz#07f7ecb08c50e094673bd20eaf7757db0162beee" + integrity sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-ic-unit@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz#2ee2da0690db7edfbc469279711b9e69495659d2" + integrity sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" -"@fortawesome/free-solid-svg-icons@^5.15.1": - version "5.15.1" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz#e1432676ddd43108b41197fee9f86d910ad458ef" - integrity sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg== +"@csstools/postcss-initial@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz#c385bd9d8ad31ad159edd7992069e97ceea4d09a" + integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg== + +"@csstools/postcss-is-pseudo-class@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz#d34e850bcad4013c2ed7abe948bfa0448aa8eb74" + integrity sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" -"@fortawesome/react-fontawesome@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" - integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== +"@csstools/postcss-light-dark-function@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz#0df448aab9a33cb9a085264ff1f396fb80c4437d" + integrity sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA== dependencies: - prop-types "^15.7.2" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -"@fullhuman/postcss-purgecss@^3.0.0": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz#47af7b87c9bfb3de4bc94a38f875b928fffdf339" - integrity sha512-kwOXw8fZ0Lt1QmeOOrd+o4Ibvp4UTEBFQbzvWldjlKv5n+G9sXfIPn1hh63IQIL8K8vbvv1oYMJiIUbuy9bGaA== +"@csstools/postcss-logical-float-and-clear@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz#62617564182cf86ab5d4e7485433ad91e4c58571" + integrity sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ== + +"@csstools/postcss-logical-overflow@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz#c6de7c5f04e3d4233731a847f6c62819bcbcfa1d" + integrity sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA== + +"@csstools/postcss-logical-overscroll-behavior@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz#43c03eaecdf34055ef53bfab691db6dc97a53d37" + integrity sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w== + +"@csstools/postcss-logical-resize@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz#4df0eeb1a61d7bd85395e56a5cce350b5dbfdca6" + integrity sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg== dependencies: - purgecss "^3.1.3" + postcss-value-parser "^4.2.0" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@csstools/postcss-logical-viewport-units@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz#016d98a8b7b5f969e58eb8413447eb801add16fc" + integrity sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ== dependencies: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/utilities" "^2.0.0" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@csstools/postcss-media-minmax@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz#184252d5b93155ae526689328af6bdf3fc113987" + integrity sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" -"@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== +"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz#f485c31ec13d6b0fb5c528a3474334a40eff5f11" + integrity sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg== dependencies: - "@nodelib/fs.scandir" "2.1.4" - fastq "^1.6.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" -"@npmcli/move-file@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" - integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== +"@csstools/postcss-nested-calc@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz#754e10edc6958d664c11cde917f44ba144141c62" + integrity sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A== dependencies: - mkdirp "^1.0.4" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" -"@tailwindcss/forms@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.2.1.tgz#3244b185854fae1a7cbe8d2456314d8b2d98cf43" - integrity sha512-czfvEdY+J2Ogfd6RUSr/ZSUmDxTujr34M++YLnp2cCPC3oJ4kFvFMaRXA6cEXKw7F1hJuapdjXRjsXIEXGgORg== +"@csstools/postcss-normalize-display-values@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz#ecdde2daf4e192e5da0c6fd933b6d8aff32f2a36" + integrity sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q== dependencies: - mini-svg-data-uri "^1.2.3" + postcss-value-parser "^4.2.0" -"@types/chart.js@^2.8.5": - version "2.8.5" - resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.8.5.tgz#7d47cfd36f0a1c2c4ad6a585749bc68e8659492a" +"@csstools/postcss-oklab-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz#416640ef10227eea1375b47b72d141495950971d" + integrity sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -"@types/codemirror@^0.0.98": - version "0.0.98" - resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.98.tgz#b35c7a4ab1fc1684b08a4e3eb65240020556ebfb" - integrity sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA== +"@csstools/postcss-progressive-custom-properties@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz#c39780b9ff0d554efb842b6bd75276aa6f1705db" + integrity sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw== dependencies: - "@types/tern" "*" + postcss-value-parser "^4.2.0" -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@csstools/postcss-random-function@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz#3191f32fe72936e361dadf7dbfb55a0209e2691e" + integrity sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@types/debounce@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" - integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== +"@csstools/postcss-relative-color-syntax@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz#ced792450102441f7c160e1d106f33e4b44181f8" + integrity sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -"@types/estree@*": - version "0.0.45" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" - integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@csstools/postcss-scope-pseudo-class@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz#9fe60e9d6d91d58fb5fc6c768a40f6e47e89a235" + integrity sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q== + dependencies: + postcss-selector-parser "^7.0.0" -"@types/events@*", "@types/events@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" +"@csstools/postcss-sign-functions@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz#a9ac56954014ae4c513475b3f1b3e3424a1e0c12" + integrity sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" +"@csstools/postcss-stepped-value-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz#36036f1a0e5e5ee2308e72f3c9cb433567c387b9" + integrity sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA== dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@types/history@*": - version "4.7.2" +"@csstools/postcss-text-decoration-shorthand@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz#fae1b70f07d1b7beb4c841c86d69e41ecc6f743c" + integrity sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA== + dependencies: + "@csstools/color-helpers" "^5.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-trigonometric-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz#3f94ed2e319b57f2c59720b64e4d0a8a6fb8c3b2" + integrity sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-unset-value@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz#7caa981a34196d06a737754864baf77d64de4bba" + integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA== + +"@csstools/selector-resolve-nested@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz#848c6f44cb65e3733e478319b9342b7aa436fac7" + integrity sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g== + +"@csstools/selector-specificity@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b" + integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== + +"@csstools/utilities@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/utilities/-/utilities-2.0.0.tgz#f7ff0fee38c9ffb5646d47b6906e0bc8868bde60" + integrity sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ== + +"@discoveryjs/json-ext@^0.6.1": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz#f13c7c205915eb91ae54c557f5e92bddd8be0e83" + integrity sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ== + +"@emotion/is-prop-valid@^0.8.2": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/is-prop-valid@^1.1.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz#e9ad47adff0b5c94c72db3669ce46de33edf28c0" + integrity sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw== + dependencies: + "@emotion/memoize" "^0.9.0" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@floating-ui/core@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" + integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== + +"@floating-ui/dom@^0.5.3": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" + integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== + dependencies: + "@floating-ui/core" "^0.7.3" + +"@floating-ui/react-dom-interactions@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" + integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== + dependencies: + "@floating-ui/react-dom" "^0.7.2" + aria-hidden "^1.1.3" + use-isomorphic-layout-effect "^1.1.1" + +"@floating-ui/react-dom@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" + integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== + dependencies: + "@floating-ui/dom" "^0.5.3" + use-isomorphic-layout-effect "^1.1.1" + +"@fontsource-variable/ibm-plex-sans@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@fontsource-variable/ibm-plex-sans/-/ibm-plex-sans-5.2.8.tgz#8947cca46a35ca91266b88ebcbcac09ff264aabc" + integrity sha512-n5PF2iFa0CZT0QYTPzxvZ39opC9LnU0zdoRccoADbs+Dtsd+lbXOZF7RNuIPHcQX1dKjF63sxnRImQIB5eD0Ag== + +"@fortawesome/fontawesome-common-types@^0.2.32": + version "0.2.32" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz#3436795d5684f22742989bfa08f46f50f516f259" + integrity sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w== + +"@fortawesome/fontawesome-svg-core@^1.2.32": + version "1.2.32" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz#da092bfc7266aa274be8604de610d7115f9ba6cf" + integrity sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/free-solid-svg-icons@^5.15.1": + version "5.15.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz#e1432676ddd43108b41197fee9f86d910ad458ef" + integrity sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/react-fontawesome@^0.1.11": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" + integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== + dependencies: + prop-types "^15.7.2" + +"@headlessui/react@^1.6.4": + version "1.6.4" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.4.tgz#c73084e23386bef5fb86cd16da3352c3a844bb4c" + integrity sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA== + +"@heroicons/react@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324" + integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ== + +"@hot-loader/react-dom@^16.14.0": + version "16.14.0" + resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.14.0.tgz#3cfc64e40bb78fa623e59b582b8f09dcdaad648a" + integrity sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + +"@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" + micromatch "^4.0.4" + pretty-format "^28.1.3" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" + +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== + dependencies: + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== + dependencies: + "@jest/test-result" "^28.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + slash "^3.0.0" + +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jsonjoy.com/base64@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-17.67.0.tgz#7eeda3cb41138d77a90408fd2e42b2aba10576d7" + integrity sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw== + +"@jsonjoy.com/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== + +"@jsonjoy.com/buffers@17.67.0", "@jsonjoy.com/buffers@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz#5c58dbcdeea8824ce296bd1cfce006c2eb167b3d" + integrity sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw== + +"@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz#8d99c7f67eaf724d3428dfd9826c6455266a5c83" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== + +"@jsonjoy.com/codegen@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz#3635fd8769d77e19b75dc5574bc9756019b2e591" + integrity sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q== + +"@jsonjoy.com/codegen@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207" + integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== + +"@jsonjoy.com/fs-core@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz#320728b4b7bef63abb60e7630351623899237411" + integrity sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz#02bac88c4968ddf2effbd7452861aaed60ba3557" + integrity sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-node-builtins@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz#a32a5bcb093f8b34a99aa8957e993a52ec316662" + integrity sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw== + +"@jsonjoy.com/fs-node-to-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz#33fc503e50d283ac5fc510e3accced7fccecf2f4" + integrity sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw== + dependencies: + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + +"@jsonjoy.com/fs-node-utils@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz#788e95052aa99744f6e8e55b5098afc203df2b9e" + integrity sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + +"@jsonjoy.com/fs-node@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz#70b18bfaf14544a9820d2016e913dde12c6de991" + integrity sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + glob-to-regex.js "^1.0.0" + thingies "^2.5.0" + +"@jsonjoy.com/fs-print@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz#7c181b9aefcc1b268be0e6233bff26310c355335" + integrity sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw== + dependencies: + "@jsonjoy.com/fs-node-utils" "4.56.10" + tree-dump "^1.1.0" + +"@jsonjoy.com/fs-snapshot@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz#05aadd2c0eaa855b13d6cb17d29b7c8cee239c8c" + integrity sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g== + dependencies: + "@jsonjoy.com/buffers" "^17.65.0" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/json-pack" "^17.65.0" + "@jsonjoy.com/util" "^17.65.0" + +"@jsonjoy.com/json-pack@^1.11.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz#93f8dd57fe3a3a92132b33d1eb182dcd9e7629fa" + integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg== + dependencies: + "@jsonjoy.com/base64" "^1.1.2" + "@jsonjoy.com/buffers" "^1.2.0" + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/json-pointer" "^1.0.2" + "@jsonjoy.com/util" "^1.9.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pack@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz#8dd8ff65dd999c5d4d26df46c63915c7bdec093a" + integrity sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w== + dependencies: + "@jsonjoy.com/base64" "17.67.0" + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/json-pointer" "17.67.0" + "@jsonjoy.com/util" "17.67.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pointer@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz#74439573dc046e0c9a3a552fb94b391bc75313b8" + integrity sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA== + dependencies: + "@jsonjoy.com/util" "17.67.0" + +"@jsonjoy.com/json-pointer@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408" + integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg== + dependencies: + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/util" "^1.9.0" + +"@jsonjoy.com/util@17.67.0", "@jsonjoy.com/util@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-17.67.0.tgz#7c4288fc3808233e55c7610101e7bb4590cddd3f" + integrity sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew== + dependencies: + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + +"@jsonjoy.com/util@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46" + integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ== + dependencies: + "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/codegen" "^1.0.0" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@noble/hashes@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz#cb5445c1bad9197d176073bf142a5c035b460640" + integrity sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + "@peculiar/asn1-x509-attr" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-csr@^2.6.0": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz#9629d403bc5a61254f28ed0b90e99cee61c0e8be" + integrity sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-ecc@^2.6.0": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz#d29c4af671508a9934edc78e7c9419fbf7bc9870" + integrity sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pfx@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz#75cddd14d43ef875109e91ea150377d679c8fbc1" + integrity sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw== + dependencies: + "@peculiar/asn1-cms" "^2.6.1" + "@peculiar/asn1-pkcs8" "^2.6.1" + "@peculiar/asn1-rsa" "^2.6.1" + "@peculiar/asn1-schema" "^2.6.0" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pkcs8@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz#bd56b4bb9e8a3702369049713a89134c87c6931a" + integrity sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pkcs9@^2.6.0": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz#ddc5222952f25b59a0562a6f8cabdb72f586a496" + integrity sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw== + dependencies: + "@peculiar/asn1-cms" "^2.6.1" + "@peculiar/asn1-pfx" "^2.6.1" + "@peculiar/asn1-pkcs8" "^2.6.1" + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + "@peculiar/asn1-x509-attr" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-rsa@^2.6.0", "@peculiar/asn1-rsa@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz#2cdf9f9ea6d6fdbaae214b9fed6de0534b654437" + integrity sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-schema@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz#0dca1601d5b0fed2a72fed7a5f1d0d7dbe3a6f82" + integrity sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg== + dependencies: + asn1js "^3.0.6" + pvtsutils "^1.3.6" + tslib "^2.8.1" + +"@peculiar/asn1-x509-attr@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz#6425008b8099476010aace5b8ae9f9cbc41db0ab" + integrity sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-x509@^2.6.0", "@peculiar/asn1-x509@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz#4e8995659e16178e0e90fe90519aa269045af262" + integrity sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + asn1js "^3.0.6" + pvtsutils "^1.3.6" + tslib "^2.8.1" + +"@peculiar/x509@^1.14.2": + version "1.14.3" + resolved "https://registry.yarnpkg.com/@peculiar/x509/-/x509-1.14.3.tgz#2c44c2b89474346afec38a0c2803ec4fb8ce959e" + integrity sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA== + dependencies: + "@peculiar/asn1-cms" "^2.6.0" + "@peculiar/asn1-csr" "^2.6.0" + "@peculiar/asn1-ecc" "^2.6.0" + "@peculiar/asn1-pkcs9" "^2.6.0" + "@peculiar/asn1-rsa" "^2.6.0" + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.0" + pvtsutils "^1.3.6" + reflect-metadata "^0.2.2" + tslib "^2.8.1" + tsyringe "^4.10.0" + +"@preact/signals-core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.2.tgz#279dcc5ab249de2f2e8f6e6779b1958256ba843e" + integrity sha512-z3/bCj7rRA21RJb4FeJ4guCrD1CQbaURHkCTunUWQpxUMAFOPXCD8tSFqERyGrrcSb4T3Hrmdc1OAl0LXBHwiw== + +"@preact/signals-react@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.2.1.tgz#6d5d305ebdb38c879043acebc65c0d9351e663c1" + integrity sha512-73J8sL1Eru7Ot4yBYOCPj1izEZjzCEXlembRgk6C7PkwsqoAVbCxMlDOFfCLoPFuJ6qeGatrJzRkcycXppMqVQ== + dependencies: + "@preact/signals-core" "^1.2.2" + use-sync-external-store "^1.2.0" + +"@sinclair/typebox@^0.24.1": + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== + +"@sinonjs/commons@^1.7.0": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tailwindcss/forms@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.2.tgz#4ef45f9916dcb37838cbe7fecdcc4ba7a7c2ab59" + integrity sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w== + dependencies: + mini-svg-data-uri "^1.2.3" + +"@tailwindcss/line-clamp@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.0.tgz#03353e31e77636b785f2336e8c978502cec1de81" + integrity sha512-HQZo6gfx1D0+DU3nWlNLD5iA6Ef4JAXh0LeD8lOGrJwEDBwwJNKQza6WoXhhY1uQrxOuU8ROxV7CqiQV4CoiLw== + +"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.11.1", "@testing-library/dom@^8.14.0": + version "8.14.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.14.0.tgz#c9830a21006d87b9ef6e1aae306cf49b0283e28e" + integrity sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" + integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + +"@testing-library/react@12.1.5": + version "12.1.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" + integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "<18.0.0" + +"@testing-library/user-event@^14.2.1": + version "14.2.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.1.tgz#8c5ff2d004544bb2220e1d864f7267fe7eb6c556" + integrity sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA== + +"@types/aria-query@^4.2.0": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/codemirror@^0.0.98": + version "0.0.98" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.98.tgz#b35c7a4ab1fc1684b08a4e3eb65240020556ebfb" + integrity sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA== + dependencies: + "@types/tern" "*" + +"@types/connect-history-api-fallback@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/debounce@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" + integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/events@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": + version "4.19.8" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz#99b960322a4d576b239a640ab52ef191989b036f" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + +"@types/express@^4.17.25": + version "4.17.25" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "^1" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/history@*": + version "4.7.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" "@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": @@ -1209,42 +2525,103 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/json-schema@^7.0.3": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz#306e3a3a73828522efa1341159da4846e7573a6c" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== + dependencies: + hoist-non-react-statics "^3.3.0" -"@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== -"@types/json-schema@^7.0.5": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/http-proxy@^1.17.8": + version "1.17.17" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.17.tgz#d9e2c4571fe3507343cb210cd41790375e59a533" + integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== + dependencies: + "@types/node" "*" -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== -"@types/minimatch@*": +"@types/istanbul-lib-report@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@*": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.3.tgz#52f3f3e50ce59191ff5fbb1084896cc0cf30c9ce" + integrity sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw== + dependencies: + jest-matcher-utils "^28.0.0" + pretty-format "^28.0.0" + +"@types/jest@^28.1.3": + version "28.1.8" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.8.tgz#6936409f3c9724ea431efd412ea0238a0f03b09b" + integrity sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw== + dependencies: + expect "^28.0.0" + pretty-format "^28.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json-schema@^7.0.8": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node@*": - version "12.6.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.9.tgz#ffeee23afdc19ab16e979338e7b536fdebbbaeaf" + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.2.3.tgz#9c18245be768bdb4ce631566c7da303a5c99a7f8" + integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ== + dependencies: + undici-types "~7.16.0" -"@types/node@^14.11.10": - version "14.11.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef" - integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA== +"@types/node@^22.0.0": + version "22.19.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.1.tgz#1188f1ddc9f46b4cc3aec76749050b4e1f459b7b" + integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ== + dependencies: + undici-types "~6.21.0" "@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/path-browserify@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/path-browserify/-/path-browserify-1.0.3.tgz#25de712d4def94b3901f033c30d3d3bd16eba8d3" + integrity sha512-ZmHivEbNCBtAfcrFeBCiTjdIc2dey0l7oCGNGpSuRTy8jP6UVND7oUowlvDujBy8r2Hoa8bfFUOCiPWfmtkfxw== + +"@types/prettier@^2.1.5": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/prop-types@*": version "15.7.1" @@ -1257,11 +2634,15 @@ dependencies: "@types/react" "*" -"@types/query-string@^6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-6.3.0.tgz#b6fa172a01405abcaedac681118e78429d62ea39" - dependencies: - query-string "*" +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/react-copy-to-clipboard@^4.3.0": version "4.3.0" @@ -1270,19 +2651,19 @@ dependencies: "@types/react" "*" -"@types/react-dom@^16.9.8": - version "16.9.8" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" - integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== +"@types/react-dom@<18.0.0": + version "17.0.17" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" + integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== dependencies: - "@types/react" "*" + "@types/react" "^17" -"@types/react-helmet@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.0.0.tgz#5b74e44a12662ffb12d1c97ee702cf4e220958cf" - integrity sha512-NBMPAxgjpaMooXa51cU1BTgrX6T+hQbMiLm77JhBbfOzPQea3RB5rNpPOD5xGWHIVpGXHd59cltEzIq0qglGcQ== +"@types/react-dom@^16.9.16": + version "16.9.16" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.16.tgz#c591f2ed1c6f32e9759dfa6eb4abfd8041f29e39" + integrity sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg== dependencies: - "@types/react" "*" + "@types/react" "^16" "@types/react-redux@^7.1.1": version "7.1.1" @@ -1315,22 +2696,91 @@ dependencies: "@types/react" "*" -"@types/react@*": - version "16.9.15" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.15.tgz#aeabb7a50f96c9e31a16079ada20ede9ed602977" +"@types/react@*", "@types/react@^17": + version "17.0.47" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.47.tgz#4ee71aaf4c5a9e290e03aa4d0d313c5d666b3b78" + integrity sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA== dependencies: "@types/prop-types" "*" - csstype "^2.2.0" + "@types/scheduler" "*" + csstype "^3.0.2" -"@types/react@^16.9.41": - version "16.9.41" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.41.tgz#925137ee4d2ff406a0ecf29e8e9237390844002e" - integrity sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug== +"@types/react@^16", "@types/react@^16.14.0": + version "16.14.26" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.26.tgz#82540a240ba7207ebe87d9579051bc19c9ef7605" + integrity sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ== dependencies: "@types/prop-types" "*" - csstype "^2.2.0" + "@types/scheduler" "*" + csstype "^3.0.2" -"@types/styled-components@^5.1.7": +"@types/retry@0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" + integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/semver@^7.3.12": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.6" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@^1", "@types/serve-static@^1.15.5": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "<1" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + +"@types/sockjs@^0.3.36": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/styled-components@5.1.7": version "5.1.7" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.7.tgz#3cd10b088c1cb1acde2e4b166b3e8275a3083710" integrity sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA== @@ -1346,271 +2796,302 @@ dependencies: "@types/estree" "*" +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" + integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== + dependencies: + "@types/jest" "*" + "@types/uuid@^3.4.5": version "3.4.5" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" dependencies: "@types/node" "*" -"@types/webpack-env@^1.15.2": - version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" - integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== +"@types/webpack-env@^1.18.8": + version "1.18.8" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.8.tgz#71f083718c094204d7b64443701d32f1db3989e3" + integrity sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A== + +"@types/ws@^8.5.10": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" + integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" "@types/yup@^0.29.3": version "0.29.3" resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.3.tgz#5a85024796bffe0eb01601bfc180fe218356dba4" integrity sha512-XxZFKnxzTfm+DR8MMBA35UUXfUPmjPpi8HJ90VZg7q/LIbtiOhVGJ26gNnATcflcpnIyf2Qm9A+oEhswaqoDpA== -"@typescript-eslint/eslint-plugin@^4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz#d82657b6ab4caa4c3f888ff923175fadc2f31f2a" - integrity sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ== - dependencies: - "@typescript-eslint/experimental-utils" "4.25.0" - "@typescript-eslint/scope-manager" "4.25.0" - debug "^4.1.1" - functional-red-black-tree "^1.0.1" - lodash "^4.17.15" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz#b2febcfa715d2c1806fd5f0335193a6cd270df54" - integrity sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.25.0" - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/typescript-estree" "4.25.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.25.0.tgz#6b2cb6285aa3d55bfb263c650739091b0f19aceb" - integrity sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg== - dependencies: - "@typescript-eslint/scope-manager" "4.25.0" - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/typescript-estree" "4.25.0" - debug "^4.1.1" - -"@typescript-eslint/scope-manager@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz#9d86a5bcc46ef40acd03d85ad4e908e5aab8d4ca" - integrity sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w== - dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - -"@typescript-eslint/types@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.25.0.tgz#0e444a5c5e3c22d7ffa5e16e0e60510b3de5af87" - integrity sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ== +"@typescript-eslint/eslint-plugin@^5": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" -"@typescript-eslint/typescript-estree@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz#942e4e25888736bff5b360d9b0b61e013d0cfa25" - integrity sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg== +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz#863e7ed23da4287c5b469b13223255d0fde6aaa7" - integrity sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg== - dependencies: - "@typescript-eslint/types" "4.25.0" - eslint-visitor-keys "^2.0.0" +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" + "@webassemblyjs/ast" "1.14.1" "@xtuc/long" "4.2.2" +"@webpack-cli/configtest@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-3.0.1.tgz#76ac285b9658fa642ce238c276264589aa2b6b57" + integrity sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA== + +"@webpack-cli/info@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-3.0.1.tgz#3cff37fabb7d4ecaab6a8a4757d3826cf5888c63" + integrity sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ== + +"@webpack-cli/serve@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-3.0.1.tgz#bd8b1f824d57e30faa19eb78e4c0951056f72f00" + integrity sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== "@xtuc/long@4.2.2": version "4.2.2" resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - -abab@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" - integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" + mime-types "~2.1.34" + negotiator "0.6.3" -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-import-phases@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" + integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-node@^1.6.1: +acorn-node@^1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== @@ -1619,62 +3100,41 @@ acorn-node@^1.6.1: acorn-walk "^7.0.0" xtend "^4.0.2" -acorn-walk@^7.0.0, acorn-walk@^7.1.1: +acorn-walk@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== - -acorn@^7.0.0, acorn@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== - -acorn@^7.4.0: +acorn@^7.0.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -aggregate-error@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" - integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" +acorn@^8.15.0, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - -ajv@^6.1.0, ajv@^6.10.2: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.12.2: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + fast-deep-equal "^3.1.3" -ajv@^6.12.4: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1684,241 +3144,266 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b" - integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ== +ajv@^8.0.0, ajv@^8.9.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@^3.0.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" -ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" +arg@^5.0.1, arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-hidden@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254" + integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA== + dependencies: + tslib "^1.0.0" -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - -array-includes@^3.1.2, array-includes@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" - integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== +array-includes@^3.1.2: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" get-intrinsic "^1.1.1" - is-string "^1.0.5" - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - dependencies: - array-uniq "^1.0.1" + is-string "^1.0.7" + +array-includes@^3.1.8: + version "3.1.9" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + get-intrinsic "^1.3.0" + is-string "^1.1.1" + math-intrinsics "^1.1.0" array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - -array.prototype.flat@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" - integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - -array.prototype.flatmap@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" - integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - function-bind "^1.1.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" +array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" -assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: - util "0.10.3" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" -assign-symbols@^1.0.0: +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + +asn1js@^3.0.6: + version "3.0.7" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.7.tgz#15f1f2f59e60f80d5b43ef14047a294a969f824f" + integrity sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ== + dependencies: + pvtsutils "^1.3.6" + pvutils "^1.1.3" + tslib "^2.8.1" + +async-function@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== -async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" +autoprefixer@^10.2.5, autoprefixer@^10.4.21: + version "10.4.22" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.22.tgz#90b27ab55ec0cf0684210d1f056f7d65dac55f16" + integrity sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg== dependencies: - lodash "^4.17.14" + browserslist "^4.27.0" + caniuse-lite "^1.0.30001754" + fraction.js "^5.3.4" + normalize-range "^0.1.2" + picocolors "^1.1.1" + postcss-value-parser "^4.2.0" -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +autoprefixer@^10.4.7: + version "10.4.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" + integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA== + dependencies: + browserslist "^4.20.3" + caniuse-lite "^1.0.30001335" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" -atob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" -autoprefixer@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.1.0.tgz#b19fd8524edef8c85c9db3bdb0c998de84e172fb" - integrity sha512-0/lBNwN+ZUnb5su18NZo5MBIjDaq6boQKZcxwy86Gip/CmXA2zZqUoFQLCNAGI5P25ZWSP2RWdhDJ8osfKEjoQ== +axios@^1.13.2: + version "1.13.5" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.5.tgz#5e464688fa127e11a660a2c49441c009f6567a43" + integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q== dependencies: - browserslist "^4.15.0" - caniuse-lite "^1.0.30001165" - colorette "^1.2.1" - fraction.js "^4.0.12" - normalize-range "^0.1.2" - postcss-value-parser "^4.1.0" + follow-redirects "^1.15.11" + form-data "^4.0.5" + proxy-from-env "^1.1.0" -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +babel-jest@^28.1.1, babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== dependencies: - follow-redirects "^1.10.0" + "@jest/transform" "^28.1.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" -babel-loader@^8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb" +babel-loader@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-10.0.0.tgz#b9743714c0e1e084b3e4adef3cd5faee33089977" + integrity sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA== dependencies: - find-cache-dir "^2.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - pify "^4.0.1" + find-up "^5.0.0" babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" @@ -1927,6 +3412,27 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" @@ -1936,29 +3442,61 @@ babel-plugin-macros@^2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" -"babel-plugin-styled-components@>= 1": - version "1.10.6" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.6.tgz#f8782953751115faf09a9f92431436912c34006b" +"babel-plugin-styled-components@>= 1.12.0": + version "2.1.4" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" + integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-module-imports" "^7.0.0" - babel-plugin-syntax-jsx "^6.18.0" - lodash "^4.17.11" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + lodash "^4.17.21" + picomatch "^2.3.1" -babel-plugin-styled-components@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz#1dec1676512177de6b827211e9eda5a30db4f9b9" - integrity sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA== +babel-plugin-styled-components@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz#c81ef34b713f9da2b7d3f5550df0d1e19e798086" + integrity sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.16.0" + "@babel/helper-module-imports" "^7.16.0" babel-plugin-syntax-jsx "^6.18.0" lodash "^4.17.11" + picomatch "^2.3.0" babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== + dependencies: + babel-plugin-jest-hoist "^28.1.3" + babel-preset-current-node-syntax "^1.0.0" + babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" @@ -1968,289 +3506,151 @@ babel-runtime@^6.26.0: regenerator-runtime "^0.11.0" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" +baseline-browser-mapping@^2.8.25, baseline-browser-mapping@^2.9.0: + version "2.9.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" + integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - -bfj@^6.1.1: - version "6.1.2" - resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f" - integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw== - dependencies: - bluebird "^3.5.5" - check-types "^8.0.3" - hoopy "^0.1.4" - tryer "^1.0.1" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - -binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" +body-parser@~1.20.3: + version "1.20.4" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== dependencies: - bytes "3.1.0" - content-type "~1.0.4" + bytes "~3.1.2" + content-type "~1.0.5" debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + depd "2.0.0" + destroy "~1.2.0" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + on-finished "~2.4.1" + qs "~6.14.0" + raw-body "~2.5.3" + type-is "~1.6.18" + unpipe "~1.0.0" -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" +bonjour-service@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" + integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== dependencies: - fill-range "^7.0.1" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" +boring-avatars@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/boring-avatars/-/boring-avatars-1.7.0.tgz#70ac7146bbf37d8e69a35544b24f1d75558f868a" + integrity sha512-ZNHd8J7C/V0IjQMGQowLJ5rScEFU23WxePigH6rqKcT2Esf0qhYvYxw8s9i3srmlfCnCV00ddBjaoGey1eNOfA== -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" + balanced-match "^1.0.0" + concat-map "0.0.1" -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" + fill-range "^7.1.1" -browserify-des@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" +browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.24.0, browserslist@^4.26.0, browserslist@^4.27.0: + version "4.28.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" + integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" + baseline-browser-mapping "^2.8.25" + caniuse-lite "^1.0.30001754" + electron-to-chromium "^1.5.249" + node-releases "^2.0.27" + update-browserslist-db "^1.1.4" -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" +browserslist@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" +browserslist@^4.8.5: + version "4.21.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" + integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + caniuse-lite "^1.0.30001358" + electron-to-chromium "^1.4.164" + node-releases "^2.0.5" + update-browserslist-db "^1.0.0" -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: - pako "~1.0.5" - -browserslist@^4.12.0: - version "4.12.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" - integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== - dependencies: - caniuse-lite "^1.0.30001088" - electron-to-chromium "^1.3.483" - escalade "^3.0.1" - node-releases "^1.1.58" - -browserslist@^4.15.0: - version "4.16.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.0.tgz#410277627500be3cb28a1bfe037586fbedf9488b" - integrity sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ== - dependencies: - caniuse-lite "^1.0.30001165" - colorette "^1.2.1" - electron-to-chromium "^1.3.621" - escalade "^3.1.1" - node-releases "^1.1.67" + fast-json-stable-stringify "2.x" -browserslist@^4.8.5: - version "4.14.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" - integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: - caniuse-lite "^1.0.30001135" - electron-to-chromium "^1.3.571" - escalade "^3.1.0" - node-releases "^1.1.61" + node-int64 "^0.4.0" buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" +bundle-name@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + run-applescript "^7.0.0" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - -bytes@3.1.0, bytes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" +bytes@3.1.2, bytes@^3.0.0, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.0.4: - version "15.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.4.tgz#b2c23cf4ac4f5ead004fb15a0efb0a20340741f1" - integrity sha512-YlnKQqTbD/6iyoJvEY3KJftjrdBYroCbxxYXzhOzsFLWlp6KX4BOlEf4mTx0cMUfVaTS3ENL2QtDWeRYoGLkkw== - dependencies: - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.0" - tar "^6.0.2" - unique-filename "^1.1.1" +bytestreamjs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/bytestreamjs/-/bytestreamjs-2.0.1.tgz#a32947c7ce389a6fa11a09a9a563d0a45889535e" + integrity sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" @@ -2260,191 +3660,173 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-css@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== camelize@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + version "1.0.1" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -caniuse-lite@^1.0.30001088, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001165: - version "1.0.30001170" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz" - integrity sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA== +caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001358: + version "1.0.30001757" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz#a46ff91449c69522a462996c6aac4ef95d7ccc5e" + integrity sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ== -chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +caniuse-lite@^1.0.30001754, caniuse-lite@^1.0.30001759: + version "1.0.30001769" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" + integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== + +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -chart.js@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.8.0.tgz#b703b10d0f4ec5079eaefdcd6ca32dc8f826e0e9" - dependencies: - chartjs-color "^2.1.0" - moment "^2.10.2" - -chartjs-color-string@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" - dependencies: - color-name "^1.0.0" - -chartjs-color@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.3.0.tgz#0e7e1e8dba37eae8415fd3db38bf572007dd958f" +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: - chartjs-color-string "^0.6.0" - color-convert "^0.5.3" - -check-types@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" - integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== + ansi-styles "^4.1.0" + supports-color "^7.1.0" -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chokidar@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" +chart.js@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" + integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== -chokidar@^3.4.2: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chokidar@^3.5.2, chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" - -chownr@^1.0.1, chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + fsevents "~2.3.2" chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - dependencies: - tslib "^1.9.0" - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== clean-set@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/clean-set/-/clean-set-1.1.2.tgz#76d8bf238c3e27827bfa73073ecdfdc767187070" integrity sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== codemirror@^5.57.0: version "5.57.0" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.57.0.tgz#d26365b72f909f5d2dbb6b1209349ca1daeb2d50" integrity sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" +collect-v8-coverage@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== -color-convert@^1.9.0, color-convert@^1.9.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" +color-convert@^1.9.0, color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: - color-name "1.1.1" + color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" @@ -2453,146 +3835,135 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" - -color-name@^1.0.0: +color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== +color-string@^1.6.0, color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" color@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" - integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== dependencies: - color-convert "^1.9.1" - color-string "^1.5.4" + color-convert "^1.9.3" + color-string "^1.6.0" -colorette@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== +color@^4.0.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colorette@^2.0.10, colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" -commander@^2.10.0, commander@^2.18.0, commander@^2.20.0: +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +commander@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -compressible@~2.0.16: - version "2.0.17" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" +compressible@~2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: - mime-db ">= 1.40.0 < 2" + mime-db ">= 1.43.0 < 2" -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" +compression@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" +content-disposition@~0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + safe-buffer "5.2.1" -convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - dependencies: - safe-buffer "~5.1.1" +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" +convert-source-map@^1.4.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" +cookie-signature@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +cookie@~0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -copy-to-clipboard@^3: +copy-to-clipboard@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== @@ -2613,8 +3984,9 @@ core-js@^2.4.0: integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmiconfig@^6.0.0: version "6.0.0" @@ -2627,55 +3999,45 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" +cosmiconfig@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" cross-env@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" - integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: cross-spawn "^7.0.1" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" +cross-fetch@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" + node-fetch "^2.6.12" -cross-spawn@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" - integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== +cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -2690,61 +4052,90 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" +css-blank-pseudo@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz#32020bff20a209a53ad71b8675852b49e8d57e46" + integrity sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag== dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" + postcss-selector-parser "^7.0.0" css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== -css-loader@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.1.tgz#62849b45a414b7bde0bfba17325a026471040eae" - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.23" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.1.1" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.2" - schema-utils "^2.6.0" +css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q== + +css-has-pseudo@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz#a5ee2daf5f70a2032f3cefdf1e36e7f52a243873" + integrity sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.2.0" + +css-loader@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8" + integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + +css-prefers-color-scheme@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz#ba001b99b8105b8896ca26fc38309ddb2278bd3c" + integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ== css-to-react-native@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" - integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== dependencies: camelize "^1.0.0" css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" css-unit-converter@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + +cssdb@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.4.2.tgz#1a367ab1904c97af0bb2c7ae179764deae7b078b" + integrity sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg== cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^2.2.0, csstype@^2.6.7: +csstype@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" @@ -2753,202 +4144,191 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8" integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" -date-fns@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" - integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" +date-fns@^2.28.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== debounce@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - -debug@^3.1.1, debug@^3.2.5: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - dependencies: - ms "^2.1.1" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: - ms "^2.1.1" + ms "^2.1.3" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" +debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: - ms "^2.1.1" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + ms "2.1.2" decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge-ts@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-4.2.1.tgz#104fe27c91abde4597bad1dcfd00a7a8be22d532" + integrity sha512-xzJLiUo4z1dD2nggSfaMvHo5qWLoy/JVa9rKuktC6FrQQEBI8Qnj7KwuCYZhqBoGOOpGqs6+3MR2ZhSMcTr4BA== + deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +deepmerge@^4.2.2, deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" +default-browser-id@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.1.tgz#f7a7ccb8f5104bf8e0f71ba3b1ccfa5eafdb21e8" + integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== -define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" +default-browser@^5.2.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.5.0.tgz#2792e886f2422894545947cc80e1a444496c5976" + integrity sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + bundle-name "^4.1.0" + default-browser-id "^5.0.0" -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== dependencies: - object-keys "^1.0.12" + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - dependencies: - is-descriptor "^0.1.0" +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: - is-descriptor "^1.0.0" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" + version "1.0.1" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" + integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== -delegates@^1.0.0: +delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" +destroy@1.2.0, destroy@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== dependencies: - acorn-node "^1.6.1" + acorn-node "^1.8.2" defined "^1.0.0" - minimist "^1.1.1" + minimist "^1.2.6" -didyoumean@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" - integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== dir-glob@^3.0.1: version "3.0.1" @@ -2957,22 +4337,17 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: - buffer-indexof "^1.0.0" + "@leichtgewicht/ip-codec" "^2.0.1" doctrine@^2.1.0: version "2.1.0" @@ -2987,6 +4362,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + dom-helpers@^5.0.1: version "5.1.3" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" @@ -2995,38 +4375,30 @@ dom-helpers@^5.0.1: csstype "^2.6.7" dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== dset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/dset/-/dset-2.0.1.tgz#a15fff3d1e4d60ac0c95634625cbd5441a76deb1" - integrity sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ== - -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + version "2.1.0" + resolved "https://registry.yarnpkg.com/dset/-/dset-2.1.0.tgz#cd1e99e55cf32366d8f144f906c42f7fb3bf431e" + integrity sha512-hlQYwNEdW7Qf8zxysy+yN1E8C/SxRst3Z9n+IvXOR35D9bPVwNHhnL8ZBeoZjvinuGrlvGg6pAMDwhmjqFDgjA== -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" easy-peasy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-4.0.1.tgz#8b3ab1ebb43509a62dc2c37b4269a9141e33d918" - integrity sha512-aTvB48M2ej6dM/wllUm1F7CTWGnYOYh82SHBkvJtOZhJ/9L8Gmg/nIVqDPwJeojOWZe+gbLtpyi8DhN6fPNBYg== + version "4.0.2" + resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-4.0.2.tgz#653662216276d0f27d91c7ce82a26ace4526da12" + integrity sha512-U68qh0GneNCeHkuyk4JScuuSJLb3xdhZhANV5Kyam7paxuz28sO8pVXaCDjHO+wcBUSB9ApmNFJUN3BqmwF4oQ== dependencies: - immer "7.0.9" + immer "^9.0.6" is-plain-object "^5.0.0" memoizerific "^1.11.3" redux "^4.0.5" @@ -3038,124 +4410,212 @@ easy-peasy@^4.0.1: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@^2.6.1: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - -electron-to-chromium@^1.3.483: - version "1.3.487" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.487.tgz#8075e6ea33ee2e79a2dfb2a2467033f014017258" - integrity sha512-m4QS3IDShxauFfYFpnEzRCcUI55oKB9acEnHCuY/hSCZMz9Pz2KJj+UBnGHxRxS/mS1aphqOQ5wI6gc3yDZ7ew== - -electron-to-chromium@^1.3.571: - version "1.3.582" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.582.tgz#1adfac5affce84d85b3d7b3dfbc4ade293a6ffc4" - integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww== - -electron-to-chromium@^1.3.621: - version "1.3.633" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.633.tgz#16dd5aec9de03894e8d14a1db4cda8a369b9b7fe" - integrity sha512-bsVCsONiVX1abkWdH7KtpuDAhsQ3N3bjPYhROSAXE78roJKet0Y5wznA14JE9pzbwSZmSMAW6KiKYf1RvbTJkA== +electron-to-chromium@^1.4.164: + version "1.5.260" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz#73f555d3e9b9fd16ff48fc406bbad84efa9b86c7" + integrity sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA== -elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" +electron-to-chromium@^1.5.249, electron-to-chromium@^1.5.263: + version "1.5.286" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" + integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== -enhanced-resolve@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" - integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== +enhanced-resolve@^5.19.0: + version "5.19.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" + graceful-fs "^4.2.4" + tapable "^2.3.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" +env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - dependencies: - prr "~1.0.1" +envinfo@^7.14.0: + version "7.20.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.20.0.tgz#3fd9de69fb6af3e777a017dfa033676368d67dd7" + integrity sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg== error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: - version "1.18.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" - integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-abstract@^1.19.0, es-abstract@^1.19.5: + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + function.prototype.name "^1.1.5" get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" has "^1.0.3" - has-symbols "^1.0.2" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.3" - is-string "^1.0.6" - object-inspect "^1.10.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" object-keys "^1.1.1" object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" + integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.6" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.4" + safe-array-concat "^1.1.3" + +es-module-lexer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" + integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-shim-unscopables@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== + dependencies: + hasown "^2.0.2" es-to-primitive@^1.2.1: version "1.2.1" @@ -3166,134 +4626,91 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" - integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" -escalade@^3.1.0, escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-standard@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz#6c8761e544e96c531ff92642eeb87842b8488516" - integrity sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg== - -eslint-import-resolver-node@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" - integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== - dependencies: - debug "^2.6.9" - resolve "^1.13.1" - -eslint-module-utils@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz#b51be1e473dd0de1c5ea638e22429c2490ea8233" - integrity sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A== - dependencies: - debug "^3.2.7" - pkg-dir "^2.0.0" - -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" +eslint-config-prettier@^8: + version "8.10.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz#0642e53625ebc62c31c24726b0f050df6bd97a2e" + integrity sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A== -eslint-plugin-import@^2.23.3: - version "2.23.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.23.3.tgz#8a1b073289fff03c4af0f04b6df956b7d463e191" - integrity sha512-wDxdYbSB55F7T5CC7ucDjY641VvKmlRwT0Vxh7PkY1mI4rclVRFWYfsrjDgZvwYYDZ5ee0ZtfFKXowWjqvEoRQ== +eslint-plugin-jest-dom@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.2.tgz#9d3e2f51055f74c74e745d89c4b1a9781e0ec7a9" + integrity sha512-Jo51Atwyo2TdcUncjmU+UQeSTKh3sc2LF/M5i/R3nTU0Djw9V65KGJisdm/RtuKhy2KH/r7eQ1n6kwYFPNdHlA== dependencies: - array-includes "^3.1.3" - array.prototype.flat "^1.2.4" - debug "^2.6.9" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.4" - eslint-module-utils "^2.6.1" - find-up "^2.0.0" - has "^1.0.3" - is-core-module "^2.4.0" - minimatch "^3.0.4" - object.values "^1.1.3" - pkg-up "^2.0.0" - read-pkg-up "^3.0.0" - resolve "^1.20.0" - tsconfig-paths "^3.9.0" + "@babel/runtime" "^7.16.3" + "@testing-library/dom" "^8.11.1" + requireindex "^1.2.0" -eslint-plugin-node@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== +eslint-plugin-prettier@^4: + version "4.2.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz#91ca3f2f01a84f1272cce04e9717550494c0fe06" + integrity sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg== dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-promise@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz#fb2188fb734e4557993733b41aa1a688f46c6f24" - integrity sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng== - -eslint-plugin-react-hooks@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" - integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== + prettier-linter-helpers "^1.0.0" -eslint-plugin-react@^7.23.2: - version "7.23.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz#2d2291b0f95c03728b55869f01102290e792d494" - integrity sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw== - dependencies: - array-includes "^3.1.3" - array.prototype.flatmap "^1.2.4" +eslint-plugin-react-hooks@^4: + version "4.6.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" + integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== + +eslint-plugin-react@^7: + version "7.37.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.3" + array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" - has "^1.0.3" + es-iterator-helpers "^1.2.1" + estraverse "^5.3.0" + hasown "^2.0.2" jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.0.4" - object.entries "^1.1.3" - object.fromentries "^2.0.4" - object.values "^1.1.3" - prop-types "^15.7.2" - resolve "^2.0.0-next.3" - string.prototype.matchall "^4.0.4" - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.1.1: + minimatch "^3.1.2" + object.entries "^1.1.9" + object.fromentries "^2.0.8" + object.values "^1.2.1" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.12" + string.prototype.repeat "^1.0.0" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -3301,98 +4718,89 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.0.0, eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" - -eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + esrecurse "^4.3.0" + estraverse "^5.2.0" -eslint@^7.27.0: - version "7.27.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7" - integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.1" - ajv "^6.10.0" +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" + optionator "^0.9.3" + strip-ansi "^6.0.1" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - dependencies: - estraverse "^4.1.0" - esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -3400,19 +4808,15 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - -estraverse@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" - integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.2" @@ -3421,120 +4825,91 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - dependencies: - original "^1.0.0" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - homedir-polyfill "^1.0.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" -express@^4.16.3, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - dependencies: - accepts "~1.3.7" +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^28.0.0, expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + +express@^4.22.1: + version "4.22.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" + integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== + dependencies: + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" + body-parser "~1.20.3" + content-disposition "~0.5.4" content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" + cookie "~0.7.1" + cookie-signature "~1.0.6" debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" + depd "2.0.0" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" + finalhandler "~1.3.1" + fresh "~0.5.2" + http-errors "~2.0.0" + merge-descriptors "1.0.3" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "~2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" + path-to-regexp "~0.1.12" + proxy-addr "~2.0.7" + qs "~6.14.0" range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" + safe-buffer "5.2.1" + send "~0.19.0" + serve-static "~1.16.2" + setprototypeof "1.2.0" + statuses "~2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1: +fast-deep-equal@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -3543,48 +4918,78 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.2.7, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.8" -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== dependencies: reusify "^1.0.4" -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" -faye-websocket@~0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: - websocket-driver ">=0.5.1" + bser "2.1.1" -figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== file-entry-cache@^6.0.1: version "6.0.1" @@ -3593,77 +4998,35 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@~6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== +file-loader@~6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== dependencies: loader-utils "^2.0.0" - schema-utils "^2.6.5" - -filesize@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + schema-utils "^3.0.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" +finalhandler@~1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" + integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" - on-finished "~2.3.0" + on-finished "~2.4.1" parseurl "~1.3.3" - statuses "~1.5.0" + statuses "~2.0.2" unpipe "~1.0.0" -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3671,15 +5034,13 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" + locate-path "^6.0.0" + path-exists "^4.0.0" flat-cache@^3.0.4: version "3.0.4" @@ -3689,187 +5050,163 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== -flush-write-stream@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - fn-name@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== -follow-redirects@^1.0.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb" - dependencies: - debug "=3.1.0" - -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" +follow-redirects@^1.0.0, follow-redirects@^1.15.11: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" -fork-ts-checker-webpack-plugin@^6.2.10: - version "6.2.10" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.2.10.tgz#800ab1fa523c76011a3413bc4e7815e45b63e826" - integrity sha512-HveFCHWSH2WlYU1tU3PkrupvW8lNFMTfH3Jk0TfC2mtktE9ibHGcifhCsCFvj+kqlDfNIlwmNLiNqR9jnSA7OQ== +form-data@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" formik@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" - integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== + version "2.4.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.9.tgz#7e5b81e9c9e215d0ce2ac8fed808cf7fba0cd204" + integrity sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og== dependencies: + "@types/hoist-non-react-statics" "^3.3.1" deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" - lodash "^4.17.14" - lodash-es "^4.17.14" + lodash "^4.17.21" + lodash-es "^4.17.21" react-fast-compare "^2.0.1" tiny-warning "^1.0.2" - tslib "^1.10.0" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + tslib "^2.0.0" -fraction.js@^4.0.12: - version "4.0.13" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" - integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.2.0: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fraction.js@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" + integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== + +framer-motion@^6.3.10: + version "6.3.10" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.3.10.tgz#e71f8c4ee09612de8328725c4fb1a1c204ae451f" + integrity sha512-modFplFb1Fznsm0MrmRAJUC32UDA5jbGU9rDvkGzhAHksru2tnoKbU/Pa3orzdsJI0CJviG4NGBrmwGveU98Cg== + dependencies: + framesync "6.0.1" + hey-listen "^1.0.8" + popmotion "11.0.3" + style-value-types "5.0.0" + tslib "^2.1.0" + optionalDependencies: + "@emotion/is-prop-valid" "^0.8.2" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" +framesync@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20" + integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA== dependencies: - map-cache "^0.2.2" + tslib "^2.1.0" -fresh@0.5.2: +fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-extra@^9.0.0, fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: - at-least-node "^1.0.0" graceful-fs "^4.2.0" jsonfile "^6.0.1" - universalify "^1.0.0" - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - dependencies: - minipass "^2.2.1" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.1.tgz#4a82f36944365e619f4454d9fff106553067b781" - integrity sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA== - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" + universalify "^2.0.0" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +function-bind@^1.1.1, function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -fsevents@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.2, functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== - -get-caller-file@^2.0.1: +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" @@ -3880,255 +5217,243 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.0, get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: - pump "^3.0.0" + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" + call-bind "^1.0.2" + get-intrinsic "^1.1.1" -glob-parent@^5.0.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== dependencies: - is-glob "^4.0.1" + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" -glob-parent@^5.1.0: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.0.0: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob-parent@^6.0.1, glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" + is-glob "^4.0.3" -glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" +glob-to-regex.js@^1.0.0, glob-to-regex.js@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz#2b323728271d133830850e32311f40766c5f6413" + integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== +glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - global@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== dependencies: min-document "^2.19.0" - process "~0.5.1" + process "^0.11.10" globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" -globals@^13.6.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== dependencies: - type-fest "^0.20.2" + define-properties "^1.2.1" + gopd "^1.0.1" -globby@^11.0.1: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graceful-fs@^4.2.0: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== gud@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" -gzip-size@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +harmony-reflect@^1.4.6: + version "1.6.2" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" + integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + +has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0: +has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - -has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" -has-symbols@^1.0.2: +has-property-descriptors@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" + es-define-property "^1.0.0" -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" + dunder-proto "^1.0.0" -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-values@^1.0.0: +has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + has-symbols "^1.0.2" -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - function-bind "^1.1.1" + has-symbols "^1.0.3" -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" + function-bind "^1.1.2" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +hey-listen@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" + integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== history@^4.9.0: version "4.9.0" @@ -4141,279 +5466,205 @@ history@^4.9.0: tiny-warning "^1.0.0" value-equal "^0.4.0" -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoist-non-react-statics@^3.0.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" - dependencies: - react-is "^16.7.0" - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - dependencies: - parse-passwd "^1.0.0" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" - integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== - -hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== - hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== dependencies: inherits "^2.0.1" obuf "^1.0.0" readable-stream "^2.0.1" wbuf "^1.1.0" -html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A== -html-parse-stringify2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== dependencies: - void-elements "^2.0.1" + void-elements "3.1.0" html-tags@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" - integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: depd "~1.1.2" inherits "2.0.4" - setprototypeof "1.1.1" + setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-parser-js@>=0.4.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" + toidentifier "1.0.1" -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" +http-errors@~2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +http-parser-js@>=0.5.1: + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + +http-proxy-middleware@^2.0.9: + version "2.0.9" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" + integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== dependencies: eventemitter3 "^4.0.0" follow-redirects "^1.0.0" requires-port "^1.0.0" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +hyperdyperid@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== -i18next-chained-backend@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/i18next-chained-backend/-/i18next-chained-backend-2.0.0.tgz#faf2e8b5f081a01e74fbec1fe580c184bc64e25b" +i18next-http-backend@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz#7c8daa31aa69679e155ec1f96a37846225bdf907" + integrity sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g== dependencies: - "@babel/runtime" "^7.4.5" + cross-fetch "4.0.0" -i18next-localstorage-backend@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/i18next-localstorage-backend/-/i18next-localstorage-backend-3.0.0.tgz#19b4e836e9a79e564631b88b8ba1c738375e636f" - dependencies: - "@babel/runtime" "^7.4.5" +i18next-multiload-backend-adapter@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/i18next-multiload-backend-adapter/-/i18next-multiload-backend-adapter-2.3.0.tgz#d3a0249671b560c186ccf74c15869f781af81b28" + integrity sha512-k52N4CImVEDeA+jJo16EJIDdlemSIsWmX6MuBPyR5V8Ig1vQSqVG1acXiQkKE+txLxD8LnR6WKYhkcxTcR86kw== -i18next-xhr-backend@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz#769124441461b085291f539d91864e3691199178" +i18next@^25.8.0: + version "25.8.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.8.0.tgz#8b4ac516db016ebba70d2b68f5ba17e6ffcd4935" + integrity sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ== dependencies: - "@babel/runtime" "^7.5.5" + "@babel/runtime" "^7.28.4" -i18next@^19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.0.0.tgz#5418207d7286128e6cfe558e659fa8c60d89794b" +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - "@babel/runtime" "^7.3.1" + safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4: +iconv-lite@~0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" - integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - dependencies: - postcss "^7.0.14" - -ieee754@^1.1.4: - version "1.1.11" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - dependencies: - minimatch "^3.0.4" - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - -ignore@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558" - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -immer@7.0.9: - version "7.0.9" - resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" - integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A== +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -import-fresh@^3.0.0: +identity-obj-proxy@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" + harmony-reflect "^1.4.6" -import-fresh@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== +immer@^9.0.6: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -4423,61 +5674,71 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -ipaddr.js@^1.9.0: +ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - dependencies: - kind-of "^3.0.2" +ipaddr.js@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" + integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== dependencies: - kind-of "^6.0.0" + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-arrayish@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd" + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== + +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-bigint@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: - binary-extensions "^1.0.0" + has-bigints "^1.0.2" is-binary-path@~2.1.0: version "2.1.0" @@ -4493,161 +5754,168 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.2" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" -is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-core-module@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== - dependencies: - has "^1.0.3" +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.2.0, is-core-module@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== +is-color-stop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA== dependencies: - has "^1.0.3" + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" +is-core-module@^2.13.0, is-core-module@^2.16.1, is-core-module@^2.8.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - kind-of "^3.0.2" + hasown "^2.0.2" -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== dependencies: - kind-of "^6.0.0" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - dependencies: - is-plain-object "^2.0.4" +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + call-bound "^1.0.3" is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-generator-function@^1.0.10: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== dependencies: - is-extglob "^2.1.0" + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== dependencies: - is-extglob "^2.1.1" + is-docker "^3.0.0" -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-network-error@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.3.0.tgz#2ce62cbca444abd506f8a900f39d20b898d37512" + integrity sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw== is-number-object@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + call-bound "^1.0.3" + has-tostringtag "^1.0.2" is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-odd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" - dependencies: - is-number "^4.0.0" - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - dependencies: - is-path-inside "^2.1.0" +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - dependencies: - path-is-inside "^1.0.2" +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" @@ -4656,105 +5924,613 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-regex@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" - integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.2" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + has-tostringtag "^1.0.0" -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" -is-string@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" - integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-symbol@^1.0.2: +is-shared-array-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: - has-symbols "^1.0.0" + call-bind "^1.0.2" + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" -is-symbol@^1.0.3: +is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" -is-windows@^1.0.1, is-windows@^1.0.2: +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" +is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterator.prototype@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== + dependencies: + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" + +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== + dependencies: + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.3.tgz#948a192d86f4e7a64c5264ad4da4877133d8792f" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== + dependencies: + "@jest/types" "^28.1.3" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" + +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" + +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.3" + jest-validate "^28.1.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.3" + graceful-fs "^4.2.9" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + natural-compare "^1.4.0" + pretty-format "^28.1.3" + semver "^7.3.5" + +jest-util@^28.0.0, jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== + dependencies: + "@jest/types" "^28.1.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.3" -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== dependencies: - isarray "1.0.0" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.3" + string-length "^4.0.1" -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" -jest-worker@^26.0.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.1.0.tgz#65d5641af74e08ccd561c240e7db61284f82f33d" - integrity sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ== +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== dependencies: + "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.0.0" + +jest@^28.1.1: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.3.tgz#e9c6a7eecdebe3548ca2b18894a50f45b36dfc6b" + integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== + dependencies: + "@jest/core" "^28.1.3" + "@jest/types" "^28.1.3" + import-local "^3.0.2" + jest-cli "^28.1.3" + +jiti@^1.21.7: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +jiti@^2.5.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema-traverse@^1.0.0: version "1.0.0" @@ -4765,29 +6541,22 @@ json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" -json3@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - dependencies: - minimist "^1.2.0" - json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +json5@^2.2.1, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== dependencies: - universalify "^1.0.0" + universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" @@ -4799,29 +6568,28 @@ jsonfile@^6.0.1: array-includes "^3.1.2" object.assign "^4.1.2" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" +launch-editor@^2.6.1: + version "2.12.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.12.0.tgz#cc740f4e0263a6b62ead2485f9896e545321f817" + integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== dependencies: - is-buffer "^1.1.5" + picocolors "^1.1.1" + shell-quote "^1.8.3" -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" @@ -4831,43 +6599,27 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" +lilconfig@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" +lilconfig@^3.1.1, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" +loader-runner@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" + integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== -loader-utils@^2.0.0, loader-utils@~2.0.0: +loader-utils@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== @@ -4876,20 +6628,14 @@ loader-utils@^2.0.0, loader-utils@~2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" +loader-utils@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" locate-path@^5.0.0: version "5.0.0" @@ -4898,145 +6644,153 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.11, lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lockfile@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" + integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA== + dependencies: + signal-exit "^3.0.2" + +lodash-es@^4.17.11, lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash.clonedeep@^4.5.0: +lodash.flatmap@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" + integrity sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg== -lodash.get@^4.0: +lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -lodash.has@^4.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - -lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - -lodash@^4.17.19, lodash@^4.17.20: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash.topath@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" + integrity sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg== -loglevel@^1.6.8: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== +lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pify "^4.0.1" - semver "^5.6.0" + yallist "^4.0.0" -make-dir@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: - semver "^6.0.0" + semver "^7.5.3" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" map-or-similar@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" + integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg== -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -memfs@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.0.tgz#f9438e622b5acd1daa8a4ae160c496fdd1325b26" - integrity sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A== - dependencies: - fs-monkey "1.0.1" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^4.43.1: + version "4.56.10" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.56.10.tgz#eaf2f6556db10f91f1e9ad9f1274fd988c646202" + integrity sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-to-fsa" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + "@jsonjoy.com/json-pack" "^1.11.0" + "@jsonjoy.com/util" "^1.9.0" + glob-to-regex.js "^1.0.1" + thingies "^2.5.0" + tree-dump "^1.0.3" + tslib "^2.0.0" memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" + integrity sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog== dependencies: map-or-similar "^1.5.0" -memory-fs@^0.4.0, memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -5044,68 +6798,62 @@ merge2@^1.3.0: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.3" + picomatch "^2.3.1" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-db@1.40.0: - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" +"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -"mime-db@>= 1.40.0 < 2": - version "1.41.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" -mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" +mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: - mime-db "1.40.0" + mime-db "^1.54.0" mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + version "2.19.2" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.2.tgz#f95db44639eaae3ac8ea85ae6809ae85ff7e3b81" + integrity sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A== dependencies: dom-walk "^0.1.0" +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + mini-create-react-context@^0.3.0: version "0.3.2" resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" @@ -5122,380 +6870,158 @@ mini-svg-data-uri@^1.2.3: minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.1.1, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34" - integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ== - dependencies: - minipass "^3.0.0" - -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== - dependencies: - yallist "^4.0.0" +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minizlib@^1.1.0: +modern-normalize@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" - dependencies: - minipass "^2.2.1" - -minizlib@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" - integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -modern-normalize@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.0.0.tgz#539d84a1e141338b01b346f3e27396d0ed17601e" - integrity sha512-1lM+BMLGuDfsdwf3rsgBSrxJwAZHFIrQ8YR61xIqdHo0uNKI9M52wNpHSrliZATJp51On6JD0AfRxd4YGSU0lw== - -moment@^2.10.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" + resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" + integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.1, ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" +ms@2.1.3, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== dependencies: - dns-packet "^1.3.1" + dns-packet "^5.2.2" thunky "^1.0.2" -nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" - -nanoid@^3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - -nanomatch@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-odd "^2.0.0" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -needle@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - -neo-async@^2.5.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== -nice-try@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-emoji@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" +node-emoji@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== dependencies: - lodash.toarray "^4.4.0" + lodash "^4.17.21" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-pre-gyp@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.1.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -node-releases@^1.1.58: - version "1.1.58" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" - integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== - -node-releases@^1.1.61: - version "1.1.63" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.63.tgz#db6dbb388544c31e888216304e8fd170efee3ff5" - integrity sha512-ukW3iCfQaoxJkSPN+iK7KznTeqDGVJatAEuXsJERYHa9tn/KaT5lBdIyxQjLEVTzSkyjJEuQ17/vaEjrOauDkg== - -node-releases@^1.1.67: - version "1.1.67" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12" - integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg== - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: - abbrev "1" - osenv "^0.1.4" + whatwg-url "^5.0.0" -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" +node-releases@^2.0.27, node-releases@^2.0.5: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -npm-bundled@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" - -npm-packlist@^1.1.6: - version "1.1.10" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - dependencies: - path-key "^2.0.0" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" + path-key "^3.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-hash@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== -object-inspect@^1.10.3, object-inspect@^1.9.0: - version "1.10.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" - integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.8, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.assign@^4.1.2: +object.assign@^4.1.0, object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== @@ -5505,124 +7031,109 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.entries@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" - integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.2" + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" -object.fromentries@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" - integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: - isobject "^3.0.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" -object.values@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" - integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== +object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.2" + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" +on-finished@^2.4.1, on-finished@~2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -opener@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" - integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" +open@^10.0.3: + version "10.2.0" + resolved "https://registry.yarnpkg.com/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c" + integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== dependencies: - is-wsl "^1.1.0" + default-browser "^5.2.1" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + wsl-utils "^0.1.0" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" - -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" + word-wrap "^1.2.5" -p-limit@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== dependencies: - p-try "^2.0.0" + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" p-limit@^2.2.0: version "2.3.0" @@ -5631,25 +7142,12 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.1.tgz#584784ac0722d1aed09f19f90ed2999af6ce2839" - integrity sha512-mw/p92EyOzl2MhauKodw54Rx5ZK4624rNfgNaBguFZkHzyUG9WsDzFF5/yQVEJinbJDdP4jEfMN+uBquiGnaLg== - dependencies: - p-try "^2.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - p-limit "^2.0.0" + yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" @@ -5658,101 +7156,48 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: - aggregate-error "^3.0.0" + p-limit "^3.0.2" -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" +p-retry@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" + integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ== dependencies: - retry "^0.12.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + "@types/retry" "0.12.2" + is-network-error "^1.0.0" + retry "^0.13.1" p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - dependencies: - cyclist "~0.2.2" - inherits "^2.0.3" - readable-stream "^2.1.5" + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== path-exists@^4.0.0: version "4.0.0" @@ -5762,27 +7207,17 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@^1.7.0: version "1.7.0" @@ -5790,110 +7225,216 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" +path-to-regexp@~0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0: +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pirates@^4.0.1, pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" +pkijs@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/pkijs/-/pkijs-3.3.3.tgz#b3f04d7b2eaacb05c81675f882be374e591626ec" + integrity sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw== dependencies: - pinkie "^2.0.0" + "@noble/hashes" "1.4.0" + asn1js "^3.0.6" + bytestreamjs "^2.0.1" + pvtsutils "^1.3.6" + pvutils "^1.1.3" + tslib "^2.8.1" -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +popmotion@11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9" + integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA== + dependencies: + framesync "6.0.1" + hey-listen "^1.0.8" + style-value-types "5.0.0" + tslib "^2.1.0" -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + +postcss-attribute-case-insensitive@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz#0c4500e3bcb2141848e89382c05b5a31c23033a3" + integrity sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw== dependencies: - find-up "^2.1.0" + postcss-selector-parser "^7.0.0" -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" +postcss-clamp@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" + integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-functional-notation@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz#9a3df2296889e629fde18b873bb1f50a4ecf4b83" + integrity sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +postcss-color-hex-alpha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz#5dd3eba1f8facb4ea306cba6e3f7712e876b0c76" + integrity sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz#5ada28406ac47e0796dff4056b0a9d5a6ecead98" + integrity sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-custom-media@^11.0.6: + version "11.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz#6b450e5bfa209efb736830066682e6567bd04967" + integrity sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" + +postcss-custom-properties@^14.0.6: + version "14.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz#1af73a650bf115ba052cf915287c9982825fc90e" + integrity sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz#9448ed37a12271d7ab6cb364b6f76a46a4a323e8" + integrity sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + postcss-selector-parser "^7.0.0" + +postcss-dir-pseudo-class@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz#80d9e842c9ae9d29f6bf5fd3cf9972891d6cc0ca" + integrity sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA== dependencies: - find-up "^3.0.0" + postcss-selector-parser "^7.0.0" -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== +postcss-double-position-gradients@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz#b482d08b5ced092b393eb297d07976ab482d4cad" + integrity sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g== dependencies: - find-up "^4.0.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= +postcss-focus-visible@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz#1f7904904368a2d1180b220595d77b6f8a957868" + integrity sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA== dependencies: - find-up "^2.1.0" + postcss-selector-parser "^7.0.0" -portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== +postcss-focus-within@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz#ac01ce80d3f2e8b2b3eac4ff84f8e15cd0057bc7" + integrity sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw== dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" + postcss-selector-parser "^7.0.0" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" +postcss-gap-properties@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz#d5ff0bdf923c06686499ed2b12e125fe64054fed" + integrity sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw== -postcss-functions@^3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" - integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= +postcss-image-set-function@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz#538e94e16716be47f9df0573b56bbaca86e1da53" + integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA== dependencies: - glob "^7.1.2" - object-assign "^4.1.1" - postcss "^6.0.9" - postcss-value-parser "^3.3.0" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-import@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" + integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" postcss-js@^3.0.3: version "3.0.3" @@ -5903,205 +7444,369 @@ postcss-js@^3.0.3: camelcase-css "^2.0.1" postcss "^8.1.6" -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" +postcss-js@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6" + integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw== dependencies: - postcss "^7.0.5" + camelcase-css "^2.0.1" -postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" +postcss-lab-function@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz#eb555ac542607730eb0a87555074e4a5c6eef6e4" + integrity sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w== dependencies: - icss-utils "^4.1.1" - postcss "^7.0.16" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -postcss-modules-scope@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz#33d4fc946602eb5e9355c4165d68a10727689dba" +postcss-load-config@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + +"postcss-load-config@^4.0.2 || ^5.0 || ^6.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== + dependencies: + lilconfig "^3.1.1" + +postcss-loader@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-8.2.0.tgz#9b830af550bc0829d565d4e774738d84df88eab7" + integrity sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA== + dependencies: + cosmiconfig "^9.0.0" + jiti "^2.5.1" + semver "^7.6.2" + +postcss-logical@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-8.1.0.tgz#4092b16b49e3ecda70c4d8945257da403d167228" + integrity sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + +postcss-modules-local-by-default@^4.0.5: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368" + integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" + integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" + postcss-selector-parser "^7.0.0" -postcss-modules-values@^3.0.0: +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + +postcss-nesting@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.2.tgz#fde0d4df772b76d03b52eccc84372e8d1ca1402e" + integrity sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ== + dependencies: + "@csstools/selector-resolve-nested" "^3.1.0" + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +postcss-opacity-percentage@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz#0b0db5ed5db5670e067044b8030b89c216e1eb0a" + integrity sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ== + +postcss-overflow-shorthand@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz#f5252b4a2ee16c68cd8a9029edb5370c4a9808af" + integrity sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q== dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" + postcss-value-parser "^4.2.0" -postcss-nested@^5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.3.tgz#2f46d77a06fc98d9c22344fd097396f5431386db" - integrity sha512-R2LHPw+u5hFfDgJG748KpGbJyTv7Yr33/2tIMWxquYuHTd9EXu27PYnKi7BxMXLtzKC0a0WVsqHtd7qIluQu/g== +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-10.0.0.tgz#ba36ee4786ca401377ced17a39d9050ed772e5a9" + integrity sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz#fa6167a307f337b2bcdd1d125604ff97cdeb5142" + integrity sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw== + dependencies: + "@csstools/postcss-alpha-function" "^1.0.1" + "@csstools/postcss-cascade-layers" "^5.0.2" + "@csstools/postcss-color-function" "^4.0.12" + "@csstools/postcss-color-function-display-p3-linear" "^1.0.1" + "@csstools/postcss-color-mix-function" "^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments" "^1.0.2" + "@csstools/postcss-content-alt-text" "^2.0.8" + "@csstools/postcss-contrast-color-function" "^2.0.12" + "@csstools/postcss-exponential-functions" "^2.0.9" + "@csstools/postcss-font-format-keywords" "^4.0.0" + "@csstools/postcss-gamut-mapping" "^2.0.11" + "@csstools/postcss-gradients-interpolation-method" "^5.0.12" + "@csstools/postcss-hwb-function" "^4.0.12" + "@csstools/postcss-ic-unit" "^4.0.4" + "@csstools/postcss-initial" "^2.0.1" + "@csstools/postcss-is-pseudo-class" "^5.0.3" + "@csstools/postcss-light-dark-function" "^2.0.11" + "@csstools/postcss-logical-float-and-clear" "^3.0.0" + "@csstools/postcss-logical-overflow" "^2.0.0" + "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" + "@csstools/postcss-logical-resize" "^3.0.0" + "@csstools/postcss-logical-viewport-units" "^3.0.4" + "@csstools/postcss-media-minmax" "^2.0.9" + "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.5" + "@csstools/postcss-nested-calc" "^4.0.0" + "@csstools/postcss-normalize-display-values" "^4.0.0" + "@csstools/postcss-oklab-function" "^4.0.12" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/postcss-random-function" "^2.0.1" + "@csstools/postcss-relative-color-syntax" "^3.0.12" + "@csstools/postcss-scope-pseudo-class" "^4.0.1" + "@csstools/postcss-sign-functions" "^1.1.4" + "@csstools/postcss-stepped-value-functions" "^4.0.9" + "@csstools/postcss-text-decoration-shorthand" "^4.0.3" + "@csstools/postcss-trigonometric-functions" "^4.0.9" + "@csstools/postcss-unset-value" "^4.0.0" + autoprefixer "^10.4.21" + browserslist "^4.26.0" + css-blank-pseudo "^7.0.1" + css-has-pseudo "^7.0.3" + css-prefers-color-scheme "^10.0.0" + cssdb "^8.4.2" + postcss-attribute-case-insensitive "^7.0.1" + postcss-clamp "^4.1.0" + postcss-color-functional-notation "^7.0.12" + postcss-color-hex-alpha "^10.0.0" + postcss-color-rebeccapurple "^10.0.0" + postcss-custom-media "^11.0.6" + postcss-custom-properties "^14.0.6" + postcss-custom-selectors "^8.0.5" + postcss-dir-pseudo-class "^9.0.1" + postcss-double-position-gradients "^6.0.4" + postcss-focus-visible "^10.0.1" + postcss-focus-within "^9.0.1" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^6.0.0" + postcss-image-set-function "^7.0.0" + postcss-lab-function "^7.0.12" + postcss-logical "^8.1.0" + postcss-nesting "^13.0.2" + postcss-opacity-percentage "^3.0.0" + postcss-overflow-shorthand "^6.0.0" + postcss-page-break "^3.0.4" + postcss-place "^10.0.0" + postcss-pseudo-class-any-link "^10.0.1" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^8.0.1" + +postcss-pseudo-class-any-link@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz#06455431171bf44b84d79ebaeee9fd1c05946544" + integrity sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz#f2df9c6ac9f95e9fe4416ca41a957eda16130172" + integrity sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA== dependencies: - postcss-selector-parser "^6.0.4" + postcss-selector-parser "^7.0.0" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" +postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" + util-deprecate "^1.0.2" -postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== +postcss-selector-parser@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== dependencies: cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" util-deprecate "^1.0.2" postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" - -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^6.0.9: - version "6.0.22" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - -postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.23, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.24" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.24.tgz#972c3c5be431b32e40caefe6c81b5a19117704c2" - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.1.6, postcss@^8.1.8, postcss@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.1.tgz#eabc5557c4558059b9d9e5b15bce7ffa9089c2a8" - integrity sha512-RhsqOOAQzTgh1UB/IZdca7F9WDb7SUCR2Vnv1x7DbvuuggQIpoDwjK+q0rzoPffhYvWNKX5JSwS4so4K3UC6vA== +postcss@^8.1.6, postcss@^8.1.8, postcss@^8.3.5, postcss@^8.4.33, postcss@^8.4.47, postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - colorette "^1.2.1" - nanoid "^3.1.20" - source-map "^0.6.1" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +pretty-format@^28.0.0, pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A== process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" -prop-types@^15.5.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== +prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" object-assign "^4.1.1" - react-is "^16.8.1" + react-is "^16.13.1" property-expr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" - integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== -proxy-addr@~2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.0" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + forwarded "0.2.0" + ipaddr.js "1.9.1" -public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" +purgecss@^4.0.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.1.3.tgz#683f6a133c8c4de7aa82fe2746d1393b214918f7" + integrity sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + commander "^8.0.0" + glob "^7.1.7" + postcss "^8.3.5" + postcss-selector-parser "^6.0.6" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" +pvtsutils@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.6.tgz#ec46e34db7422b9e4fdc5490578c1883657d6001" + integrity sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg== dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" + tslib "^2.8.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - -purgecss@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-3.1.3.tgz#26987ec09d12eeadc318e22f6e5a9eb0be094f41" - integrity sha512-hRSLN9mguJ2lzlIQtW4qmPS2kh6oMnA9RxdIYK8sz18QYqd6ePp4GNDl18oWHA1f2v2NEQIh51CO8s/E3YGckQ== - dependencies: - commander "^6.0.0" - glob "^7.0.0" - postcss "^8.2.1" - postcss-selector-parser "^6.0.2" +pvutils@^1.1.3: + version "1.1.5" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.5.tgz#84b0dea4a5d670249aa9800511804ee0b7c2809c" + integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA== qr.js@0.0.0: version "0.0.0" @@ -6117,40 +7822,22 @@ qrcode.react@^1.0.1: prop-types "^15.6.0" qr.js "0.0.0" -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - -query-string@*, query-string@^6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.7.0.tgz#7e92bf8525140cf8c5ebf500f26716b0de5b7023" +qs@~6.14.0: + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: - decode-uri-component "^0.2.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + side-channel "^1.1.0" queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - dependencies: - safe-buffer "^5.1.0" +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== randombytes@^2.1.0: version "2.1.0" @@ -6159,49 +7846,25 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.1.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-async-script@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf" +raw-body@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== dependencies: - hoist-non-react-statics "^3.3.0" - prop-types "^15.5.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" -react-copy-to-clipboard@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz#d82a437e081e68dfca3761fbd57dbf2abdda1316" - integrity sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg== - dependencies: - copy-to-clipboard "^3" - prop-types "^15.5.8" +react-chartjs-2@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz#bc5693a8b161f125301cf28ab0fe980d7dce54aa" + integrity sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw== "react-dom@npm:@hot-loader/react-dom": version "16.11.0" @@ -6215,47 +7878,55 @@ react-copy-to-clipboard@^5.0.2: react-fast-compare@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-google-recaptcha@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz#3276b29659493f7ca2a5b7739f6c239293cdf1d8" - dependencies: - prop-types "^15.5.0" - react-async-script "^1.1.1" - react-hot-loader@^4.12.21: - version "4.12.21" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.21.tgz#332e830801fb33024b5a147d6b13417f491eb975" - integrity sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA== + version "4.13.1" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.1.tgz#979fd7598e27338b3faffae6ed01c65374dace5e" + integrity sha512-ZlqCfVRqDJmMXTulUGic4lN7Ic1SXgHAFw7y/Jb7t25GBgTR0fYAJ8uY4mrpxjRyWGWmqw77qJQGnYbzCvBU7g== dependencies: fast-levenshtein "^2.0.6" global "^4.3.0" hoist-non-react-statics "^3.3.0" - loader-utils "^1.1.0" + loader-utils "^2.0.3" prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" shallowequal "^1.1.0" source-map "^0.7.3" -react-i18next@^11.2.1: - version "11.2.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.2.1.tgz#a56d9f1f52d003eb4fa8f1c7d6752123827160f0" +react-i18next@^16.5.4: + version "16.5.4" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-16.5.4.tgz#4f458b1baab274dca439ab67629c854203bf97c6" + integrity sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g== dependencies: - "@babel/runtime" "^7.3.1" - html-parse-stringify2 "2.0.1" + "@babel/runtime" "^7.28.4" + html-parse-stringify "^3.0.1" + use-sync-external-store "^1.6.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-router-dom@^5.1.2: version "5.1.2" @@ -6294,35 +7965,26 @@ react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^16.14.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" + prop-types "^15.6.2" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" + pify "^2.3.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -6333,32 +7995,18 @@ read-pkg@^3.0.0: util-deprecate "~1.0.1" readable-stream@^3.0.6: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== - dependencies: - picomatch "^2.2.1" - -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -6367,10 +8015,25 @@ reaptcha@^1.7.2: resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.7.2.tgz#d829f54270c241f46501e92a5a7badeb1fcf372d" integrity sha512-/RXiPeMd+fPUGByv+kAaQlCXCsSflZ9bKX5Fcwv9IYGS1oyT2nntL/8zn9IaiUFHL66T1jBtOABcb92g2+3w8w== -reduce-css-calc@^2.1.6: - version "2.1.7" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" - integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== dependencies: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" @@ -6380,17 +8043,11 @@ redux-devtools-extension@^2.13.8: resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" redux-thunk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + version "2.4.2" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" + integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== redux@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" - dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" - -redux@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== @@ -6398,6 +8055,32 @@ redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +redux@^4.0.5: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -6414,14 +8097,10 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" - regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regenerator-transform@^0.14.2: version "0.14.5" @@ -6430,29 +8109,26 @@ regenerator-transform@^0.14.2: dependencies: "@babel/runtime" "^7.8.4" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" + functions-have-names "^1.2.2" -regexpp@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" - -regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" regexpu-core@^4.7.1: version "4.7.1" @@ -6478,133 +8154,115 @@ regjsparser@^0.6.4: dependencies: jsesc "~0.5.0" -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" +requireindex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" + integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - dependencies: - resolve-from "^3.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-from@^3.0.0: +resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.3.2, resolve@^1.8.1: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - dependencies: - path-parse "^1.0.6" - -resolve@^1.12.0, resolve@^1.13.1: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" +resolve.exports@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== -resolve@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" - integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== +resolve@^1.1.7, resolve@^1.8.1: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.1.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.8: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.3: - version "2.0.0-next.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" - integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - dependencies: - glob "^7.1.3" +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w== + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" +run-applescript@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911" + integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== run-parallel@^1.1.9: version "1.2.0" @@ -6613,35 +8271,53 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - dependencies: - aproba "^1.1.1" - rxjs-compat@^6.5.4: version "6.6.3" resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.6.3.tgz#141405fcee11f48718d428b99c8f01826f594e5c" integrity sha512-y+wUqq7bS2dG+7rH2fNMoxsDiJ32RQzFxZQE/JdtpnmEZmwLQrb1tCiItyHxdXJHXjmHnnzFscn3b6PEmORGKw== -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex@^1.1.0: +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== dependencies: - ret "~0.1.10" + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - scheduler@^0.17.0: version "0.17.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" @@ -6649,161 +8325,173 @@ scheduler@^0.17.0: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@2.7.0, schema-utils@^2.6.5, schema-utils@^2.6.6: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" + loose-envify "^1.1.0" + object-assign "^4.1.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" +schema-utils@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" -schema-utils@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.1.tgz#eb78f0b945c7bcfa2082b3565e8db3548011dc4f" +schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== dependencies: - ajv "^6.10.2" - ajv-keywords "^3.4.1" + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" +selfsigned@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-5.5.0.tgz#4c9ab7c7c9f35f18fb6a9882c253eb0e6bd6557b" + integrity sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew== dependencies: - node-forge "0.9.0" - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + "@peculiar/x509" "^1.14.2" + pkijs "^3.3.3" semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" +semver@7.x, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^5.5.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2.1, semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" +send@~0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" + fresh "~0.5.2" + http-errors "~2.0.1" mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" + ms "2.1.3" + on-finished "~2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "~2.0.2" -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" - integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + version "1.9.2" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.2.tgz#2988e3612106d78a5e4849ddff552ce7bd3d9bcb" + integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== dependencies: - accepts "~1.3.4" + accepts "~1.3.8" batch "0.6.1" debug "2.6.9" escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + http-errors "~1.8.0" + mime-types "~2.1.35" + parseurl "~1.3.3" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" +serve-static@~1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + send "~0.19.1" -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" +setprototypeof@1.2.0, setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + kind-of "^6.0.2" shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== shebang-command@^2.0.0: version "2.0.0" @@ -6812,15 +8500,45 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -6830,158 +8548,103 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== dependencies: is-arrayish "^0.3.1" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - sockette@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/sockette/-/sockette-2.0.6.tgz#63b533f3cfe3b592fc84178beea6577fa18cebf3" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== dependencies: - faye-websocket "^0.10.0" - uuid "^3.4.0" - websocket-driver "0.6.5" + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" -source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-map-js@^1.0.2, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -source-map-loader@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.0.1.tgz#703df5345b0816734f0336c1ccee8af66e082061" - integrity sha512-DE4CJyfCVoxFLsHyuVE9Sjcib8cs5qdmOq3wcev1Un/r6F2AfQJDhag4rzpPPA48A2QZyV3CTbc+NGoFMfKIOQ== +source-map-loader@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz#f593a916e1cc54471cfc8851b905c8a845fc7e38" + integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== dependencies: - data-urls "^2.0.0" - iconv-lite "^0.5.1" - loader-utils "^2.0.0" - schema-utils "^2.6.6" - source-map "^0.6.0" + iconv-lite "^0.6.3" + source-map-js "^1.0.2" -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== dependencies: - atob "^2.1.1" + atob "^2.1.2" decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" -source-map-support@~0.5.12: +source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - -source-map@^0.5.0, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: debug "^4.1.0" detect-node "^2.0.4" @@ -7001,257 +8664,263 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== - dependencies: - minipass "^3.1.1" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" + escape-string-regexp "^2.0.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" +statuses@~2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== -stream-each@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" + es-errors "^1.3.0" + internal-slot "^1.1.0" -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + char-regex "^1.0.2" + strip-ansi "^6.0.0" string-similarity@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" - integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" + version "4.0.4" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" + integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== -string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.matchall@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz#59370644e1db7e4c0c045277690cf7b01203c4da" - integrity sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q== + strip-ansi "^6.0.1" + +string.prototype.matchall@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" + set-function-name "^2.0.2" + side-channel "^1.1.0" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== dependencies: - call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.2" - get-intrinsic "^1.1.1" - has-symbols "^1.0.2" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.3.1" - side-channel "^1.0.4" - -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - safe-buffer "~5.1.0" + safe-buffer "~5.2.0" string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - ansi-regex "^2.0.0" + ansi-regex "^5.0.1" -strip-ansi@^4.0.0: +strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - dependencies: - ansi-regex "^4.1.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-bom@^3.0.0: +strip-indent@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-json-comments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" - integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +style-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5" + integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA== -style-loader@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" - integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== +style-value-types@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad" + integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA== dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.6" + hey-listen "^1.0.8" + tslib "^2.1.0" styled-components-breakpoint@^3.0.0-preview.20: version "3.0.0-preview.20" resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-3.0.0-preview.20.tgz#877e88a00c0cf66976f610a1d347839a1a0b6d70" -styled-components@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a" - integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ== +styled-components@^5.3.0: + version "5.3.11" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" + integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^0.8.8" + "@emotion/is-prop-valid" "^1.1.0" "@emotion/stylis" "^0.8.4" "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1" + babel-plugin-styled-components ">= 1.12.0" css-to-react-native "^3.0.0" hoist-non-react-statics "^3.0.0" shallowequal "^1.1.0" supports-color "^5.5.0" -supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +sucrase@^3.35.0: + version "3.35.1" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - has-flag "^3.0.0" + has-flag "^4.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -svg-url-loader@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/svg-url-loader/-/svg-url-loader-6.0.0.tgz#b94861d9f6badfb8ca3e7d3ec4655c1bf732ac5d" - integrity sha512-Qr5SCKxyxKcRnvnVrO3iQj9EX/v40UiGEMshgegzV7vpo3yc+HexELOdtWcA3MKjL8IyZZ1zOdcILmDEa/8JJQ== +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-url-loader@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/svg-url-loader/-/svg-url-loader-8.0.0.tgz#05d57af5b19d7caa39624a88e3cd535243634991" + integrity sha512-5doSXvl18hY1fGsRLdhWAU5jgzgxJ06/gc/26cpuDnN0xOz1HmmfhkpL29SSrdIvhtxQ1UwGzmk7wTT/l48mKw== dependencies: - file-loader "~6.0.0" - loader-utils "~2.0.0" + file-loader "~6.2.0" swr@^0.2.3: version "0.2.3" @@ -7263,273 +8932,316 @@ swr@^0.2.3: symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== symbol-observable@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== -synchronous-promise@^2.0.10: - version "2.0.13" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" - integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== - -table@^6.0.9: - version "6.7.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" - integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" +synchronous-promise@^2.0.13: + version "2.0.17" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032" + integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== -tailwindcss@^2.0.1, tailwindcss@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.2.tgz#28e1573d29dd4547b26782facb05bcfaa92be366" - integrity sha512-nO9JRE1pO7SF9RnYAl6g7uzeHdrmKAFqNjT9NtZUfxqimJZAOOLOEyIEUiMq12+xIc7mC2Ey3Vf90XjHpWKfbw== +tailwindcss@^2.2.7: + version "2.2.19" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.19.tgz#540e464832cd462bb9649c1484b0a38315c2653c" + integrity sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw== dependencies: - "@fullhuman/postcss-purgecss" "^3.0.0" + arg "^5.0.1" bytes "^3.0.0" - chalk "^4.1.0" - color "^3.1.3" + chalk "^4.1.2" + chokidar "^3.5.2" + color "^4.0.1" + cosmiconfig "^7.0.1" detective "^5.2.0" - didyoumean "^1.2.1" - fs-extra "^9.0.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.7" + fs-extra "^10.0.0" + glob-parent "^6.0.1" html-tags "^3.1.0" - lodash "^4.17.20" - modern-normalize "^1.0.0" - node-emoji "^1.8.1" - object-hash "^2.0.3" - postcss-functions "^3" + is-color-stop "^1.1.0" + is-glob "^4.0.1" + lodash "^4.17.21" + lodash.topath "^4.5.2" + modern-normalize "^1.1.0" + node-emoji "^1.11.0" + normalize-path "^3.0.0" + object-hash "^2.2.0" postcss-js "^3.0.3" - postcss-nested "^5.0.1" - postcss-selector-parser "^6.0.4" + postcss-load-config "^3.1.0" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.6" postcss-value-parser "^4.1.0" pretty-hrtime "^1.0.3" - reduce-css-calc "^2.1.6" - resolve "^1.19.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - -tar@^4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -tar@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" - integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-3.0.6.tgz#db0a108bbdd3680d72c9b491fbabad09ba207b99" - integrity sha512-z3HLOOPUHkCNGkeEHqqiMAIy1pjpHwS1o+i6Zn0Ws3EAvHJj46737efNNEvJ0Vx9BdDQM83d56qySDJOSORA0A== - dependencies: - cacache "^15.0.4" - find-cache-dir "^3.3.1" - jest-worker "^26.0.0" - p-limit "^3.0.1" - schema-utils "^2.6.6" - serialize-javascript "^4.0.0" - source-map "^0.6.1" - terser "^4.8.0" - webpack-sources "^1.4.3" + purgecss "^4.0.3" + quick-lru "^5.1.1" + reduce-css-calc "^2.1.8" + resolve "^1.20.0" + tmp "^0.2.1" + +tailwindcss@^3.0.24: + version "3.4.18" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.18.tgz#9fa9650aace186644b608242f1e57d2d55593301" + integrity sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.6.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.2" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.7" + lilconfig "^3.1.3" + micromatch "^4.0.8" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.47" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.2 || ^5.0 || ^6.0" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" + +tapable@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -terser@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - dependencies: +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@^5.3.14: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser-webpack-plugin@^5.3.16: + version "5.3.16" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" + integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.31.1: + version "5.46.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.15.0" commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" + source-map-support "~0.5.20" -terser@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -thunky@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" + thenify ">= 3.1.0 < 4" -timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: - setimmediate "^1.0.4" + any-promise "^1.0.0" + +thingies@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/thingies/-/thingies-2.5.0.tgz#5f7b882c933b85989f8466b528a6247a6881e04f" + integrity sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== tiny-invariant@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tinyglobby@^0.2.11: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tmp@^0.2.1: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" +toidentifier@1.0.1, toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== toposort@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== -tr46@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" - integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== - dependencies: - punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" - integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +tree-dump@^1.0.3, tree-dump@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" + integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== + +ts-essentials@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.1.2.tgz#46db6944b73b4cd603f3d959ef1123c16ba56f59" + integrity sha512-EaSmXsAhEiirrTY1Oaa7TSpei9dzuCuFPmjKRJRPamERYtfaGS8/KpOSbjergLz/Y76/aZlV9i/krgzsuWEBbg== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +ts-jest@^28.0.5: + version "28.0.8" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.8.tgz#cd204b8e7a2f78da32cf6c95c9a6165c5b99cc73" + integrity sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^28.0.0" + json5 "^2.2.1" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^21.0.1" ts-toolbelt@^8.0.7: - version "8.0.7" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.0.7.tgz#4dad2928831a811ee17dbdab6eb1919fc0a295bf" - integrity sha512-KICHyKxc5Nu34kyoODrEe2+zvuQQaubTJz7pnC5RQ19TH/Jged1xv+h8LBrouaSD310m75oAljYs59LNHkLDkQ== + version "8.4.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.4.0.tgz#af6e64b87be3e097fac63600ce9556e78542fa27" + integrity sha512-hnGJXIgC49ZuF5g5oDthoge8t4cvT0dYg2pYO5C6yV/HmUUy1koooU2U/5K2N+Uw++hcXQpJAToLRa+GRp28Sg== -tsconfig-paths@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" - integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.0" - strip-bom "^3.0.0" +tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^1.10.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^2.0.0, tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@^1.8.1, tslib@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" +tslib@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" +tsyringe@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.10.0.tgz#d0c95815d584464214060285eaaadd94aa03299c" + integrity sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw== + dependencies: + tslib "^1.9.3" -twin.macro@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-2.0.7.tgz#b8a6f770c95032e0a532e47143b78ee3c2bbd55b" - integrity sha512-AXLs9WAf3njusUVDMS6EGZOpxatsBokgSF6xTie/2hZ1MVHlDFz2ervL6O1FqiaWtu7Pi9/8HJLJILAmAUwlGg== +twin.macro@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-2.8.2.tgz#7f1344b4b1c3811da93a62fa204fe08999df7a75" + integrity sha512-2Vg09mp+nA70AWUedJ8WRgB2me3buq7JGbOnjHnFnNaBzomVu5k7lJ9YGpByIlre+UYr7QRhtlj7+IUKxvCrUA== dependencies: "@babel/parser" "^7.12.5" + "@babel/template" "^7.14.5" + autoprefixer "^10.2.5" babel-plugin-macros "^2.8.0" chalk "^4.1.0" clean-set "^1.1.1" color "^3.1.3" dset "^2.0.1" + lodash.flatmap "^4.5.0" + lodash.get "^4.4.2" lodash.merge "^4.6.2" postcss "^8.1.8" string-similarity "^4.0.3" - tailwindcss "^2.0.1" + tailwindcss "^2.2.7" timsort "^0.3.0" type-check@^0.4.0, type-check@~0.4.0: @@ -7539,42 +9251,109 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" mime-types "~2.1.24" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" -typescript@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + +typescript@~5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -7595,127 +9374,94 @@ unicode-property-aliases-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" -union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^0.4.3" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" - dependencies: - imurmurhash "^0.1.4" - -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" +update-browserslist-db@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== dependencies: - has-value "^0.3.1" - isobject "^3.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" +update-browserslist-db@^1.1.4, update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" +use-fit-text@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/use-fit-text/-/use-fit-text-2.4.0.tgz#d3d1cd72f6d29cfb2233ec0e0b38c6f25d319f67" + integrity sha512-Iy4LMrXcdxWlyZ5phntMpJMgyXGB1p3tV73y2r0QrZ6f/thPh+/QU3ie6RCXmjF8tHMs20FKMPskXeDYIla/Ww== dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" + resize-observer-polyfill "^1.5.1" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" +use-isomorphic-layout-effect@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== use-memo-one@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" - integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" + integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== -use@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" - dependencies: - kind-of "^6.0.2" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + +use-sync-external-store@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - dependencies: - inherits "2.0.3" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - -uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" + integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" value-equal@^0.4.0: version "0.4.0" @@ -7724,216 +9470,181 @@ value-equal@^0.4.0: vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vm-browserify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" - -void-elements@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: - chokidar "^2.1.8" + makeerror "1.0.12" -watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== +watchpack@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: + glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.0" - watchpack-chokidar2 "^2.0.0" wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== dependencies: minimalistic-assert "^1.0.0" -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webpack-assets-manifest@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" - integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== - dependencies: - chalk "^2.0" - lodash.get "^4.0" - lodash.has "^4.0" - mkdirp "^0.5" - schema-utils "^1.0.0" - tapable "^1.0.0" - webpack-sources "^1.0.0" - -webpack-bundle-analyzer@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz#ce6b3f908daf069fd1f7266f692cbb3bded9ba16" - integrity sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - bfj "^6.1.1" - chalk "^2.4.1" - commander "^2.18.0" - ejs "^2.6.1" - express "^4.16.3" - filesize "^3.6.1" - gzip-size "^5.0.0" - lodash "^4.17.15" - mkdirp "^0.5.1" - opener "^1.5.1" - ws "^6.0.0" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= -webpack-cli@^3.3.12: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== +webpack-assets-manifest@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-6.4.0.tgz#e04b69394d2409a64f0cb11f00c49cab4a788540" + integrity sha512-7BDswzFlfqEP63E3uF5J1V4g4JYN0I5PQecFqLZ1OP9qVzlfzDaX4G4RK6GEeUYylv+W86dycbOkHHGfxKdLTA== dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" + deepmerge "^4.3.1" + lockfile "^1.0.4" + schema-utils "^4.3.3" + tapable "^2.3.0" + +webpack-cli@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-6.0.1.tgz#a1ce25da5ba077151afd73adfa12e208e5089207" + integrity sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw== + dependencies: + "@discoveryjs/json-ext" "^0.6.1" + "@webpack-cli/configtest" "^3.0.1" + "@webpack-cli/info" "^3.0.1" + "@webpack-cli/serve" "^3.0.1" + colorette "^2.0.14" + commander "^12.1.0" + cross-spawn "^7.0.3" + envinfo "^7.14.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^6.0.1" + +webpack-dev-middleware@^7.4.2: + version "7.4.5" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz#d4e8720aa29cb03bc158084a94edb4594e3b7ac0" + integrity sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA== + dependencies: + colorette "^2.0.10" + memfs "^4.43.1" + mime-types "^3.0.1" + on-finished "^2.4.1" range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" + schema-utils "^4.0.0" + +webpack-dev-server@^5.2.2: + version "5.2.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz#7f36a78be7ac88833fd87757edee31469a9e47d3" + integrity sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ== + dependencies: + "@types/bonjour" "^3.5.13" + "@types/connect-history-api-fallback" "^1.5.4" + "@types/express" "^4.17.25" + "@types/express-serve-static-core" "^4.17.21" + "@types/serve-index" "^1.9.4" + "@types/serve-static" "^1.15.5" + "@types/sockjs" "^0.3.36" + "@types/ws" "^8.5.10" + ansi-html-community "^0.0.8" + bonjour-service "^1.2.1" + chokidar "^3.6.0" + colorette "^2.0.10" + compression "^1.8.1" + connect-history-api-fallback "^2.0.0" + express "^4.22.1" + graceful-fs "^4.2.6" + http-proxy-middleware "^2.0.9" + ipaddr.js "^2.1.0" + launch-editor "^2.6.1" + open "^10.0.3" + p-retry "^6.2.0" + schema-utils "^4.2.0" + selfsigned "^5.5.0" serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" + sockjs "^0.3.24" spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" + webpack-dev-middleware "^7.4.2" + ws "^8.18.0" -webpack-sources@^1.0.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.43.0: - version "4.43.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" - integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" +webpack-merge@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" + integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.1" + +webpack-sources@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" + integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== + +webpack@^5.103.0: + version "5.105.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.2.tgz#f3b76f9fc36f1152e156e63ffda3bbb82e6739ea" + integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.8" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.15.0" + acorn-import-phases "^1.0.3" + browserslist "^4.28.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.1" - webpack-sources "^1.4.1" - -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: - version "0.7.0" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + enhanced-resolve "^5.19.0" + es-module-lexer "^2.0.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.3.1" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.3" + tapable "^2.3.0" + terser-webpack-plugin "^5.3.16" + watchpack "^2.5.1" + webpack-sources "^3.3.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: - http-parser-js ">=0.4.0" + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-url@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.1.0.tgz#c628acdcf45b82274ce7281ee31dd3c839791771" - integrity sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= dependencies: - lodash.sortby "^4.7.0" - tr46 "^2.0.2" - webidl-conversions "^5.0.0" + tr46 "~0.0.3" + webidl-conversions "^3.0.0" which-boxed-primitive@^1.0.2: version "1.0.2" @@ -7946,15 +9657,58 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: - isexe "^2.0.0" + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" which@^2.0.1: version "2.0.2" @@ -7963,59 +9717,59 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - dependencies: - string-width "^1.0.2 || 2" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wildcard@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - dependencies: - errno "~0.1.7" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^6.0.0, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" +write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: - async-limiter "~1.0.0" + imurmurhash "^0.1.4" + signal-exit "^3.0.7" -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +ws@^8.18.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== + +wsl-utils@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/wsl-utils/-/wsl-utils-0.1.0.tgz#8783d4df671d4d50365be2ee4c71917a0557baab" + integrity sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw== + dependencies: + is-wsl "^3.1.0" xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-attach@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.6.0.tgz#220c23addd62ab88c9914e2d4c06f7407e44680e" - integrity sha512-Mo8r3HTjI/EZfczVCwRU6jh438B4WLXxdFO86OB7bx0jGhwh2GdF4ifx/rP+OB+Cb2vmLhhVIZ00/7x3YSP3dg== - -xterm-addon-fit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz#06e0c5d0a6aaacfb009ef565efa1c81e93d90193" - integrity sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w== +xterm-addon-fit@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" + integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== xterm-addon-search-bar@^0.2.0: version "0.2.0" @@ -8025,80 +9779,78 @@ xterm-addon-search-bar@^0.2.0: babel-runtime "^6.26.0" rxjs-compat "^6.5.4" -xterm-addon-search@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0.tgz#c929d3e5cbb335e82bff72f158ea82936d9cd4ef" - integrity sha512-6060evmJJ+tZcjnx33FXaeEHLpuXEa7l9UzUsYfMlCKbu88AbE+5LJocTKCHYd71cwCwb9pjmv/G1o9Rf9Zbcg== +xterm-addon-search@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" + integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== -xterm-addon-web-links@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03" - integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg== +xterm-addon-unicode11@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0.tgz#733fd17bdf2ae6e818493db1d41241c999de0786" + integrity sha512-5pkb8YoS/deRtNqQRw8t640mu+Ga8B2MG3RXGQu0bwgcfr8XiXIRI880TWM49ICAHhTmnOLPzIIBIjEnCq7k2A== -xterm@^4.15.0: - version "4.15.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0.tgz#e52038507eba7e0d36d47f81e29fe548c82b9561" - integrity sha512-Ik1GoSq1yqKZQ2LF37RPS01kX9t4TP8gpamUYblD09yvWX5mEYuMK4CcqH6+plgiNEZduhTz/UrcaWs97gOlOw== +xterm-addon-web-links@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.6.0.tgz#0296cb6c99588847894670d998c9ea6a6aeb26ee" + integrity sha512-H6XzjWWZu8FBo+fnYpxdPk9w5M6drbsvwPEJZGRS38MihiQaVFpKlCMKdfRgDbKGE530tw1yH54rhpZfHgt2/A== -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" +xterm@^4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.7.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yarn-deduplicate@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d" - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - commander "^2.10.0" - semver "^5.3.0" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== yup@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9" - integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ== + version "0.29.3" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" + integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== dependencies: - "@babel/runtime" "^7.9.6" + "@babel/runtime" "^7.10.5" fn-name "~3.0.0" lodash "^4.17.15" lodash-es "^4.17.11" property-expr "^2.0.2" - synchronous-promise "^2.0.10" + synchronous-promise "^2.0.13" toposort "^2.0.2"