diff --git a/.env.development b/.env.development index 767930c962..61a092e9a1 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,7 @@ LOCAL_DOMAIN=localhost ALTERNATE_DOMAINS=mastodon.internal -DB_HOST=$(pwd)/data/postgres +DB_HOST=$PWD/data/postgres DB_USER=mastodon DB_NAME=mastodon_dev REDIS_URL=unix://./data/redis/redis-dev.sock diff --git a/.eslintrc.js b/.eslintrc.js index 206faa1c7a..60d2ad6169 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -247,7 +247,12 @@ module.exports = { }, // Internal packages { - pattern: '{mastodon/**,flavours/glitch-soc/**}', + pattern: '{mastodon/**}', + group: 'internal', + position: 'after', + }, + { + pattern: '{flavours/glitch-soc/**}', group: 'internal', position: 'after', }, @@ -256,6 +261,18 @@ module.exports = { }, ], + // Forbid imports from vanilla in glitch flavour + 'import/no-restricted-paths': [ + 'error', + { + zones: [{ + target: 'app/javascript/flavours/glitch/', + from: 'app/javascript/mastodon/', + message: 'Import from /flavours/glitch/ instead' + }] + } + ], + 'promise/always-return': 'off', 'promise/catch-or-return': [ 'error', @@ -325,8 +342,8 @@ module.exports = { extends: [ 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict-type-checked', + 'plugin:@typescript-eslint/stylistic-type-checked', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended', @@ -338,7 +355,7 @@ module.exports = { ], parserOptions: { - project: './tsconfig.json', + project: true, tsconfigRootDir: __dirname, }, @@ -348,6 +365,7 @@ module.exports = { '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], '@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/consistent-type-imports': 'error', + "@typescript-eslint/prefer-nullish-coalescing": ['error', {ignorePrimitives: {boolean: true}}], 'jsdoc/require-jsdoc': 'off', diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml deleted file mode 100644 index 22f51f7bdf..0000000000 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Bug Report -description: If something isn't working as expected -labels: [bug] -body: - - type: markdown - attributes: - value: | - Make sure that you are submitting a new bug that was not previously reported or already fixed. - - Please use a concise and distinct title for the issue. - - type: textarea - attributes: - label: Steps to reproduce the problem - description: What were you trying to do? - value: | - 1. - 2. - 3. - ... - validations: - required: true - - type: input - attributes: - label: Expected behaviour - description: What should have happened? - validations: - required: true - - type: input - attributes: - label: Actual behaviour - description: What happened? - validations: - required: true - - type: textarea - attributes: - label: Detailed description - validations: - required: false - - type: textarea - attributes: - label: Specifications - description: | - What version or commit hash of Mastodon did you find this bug in? - - If a front-end issue, what browser and operating systems were you using? - placeholder: | - Mastodon 3.5.3 (or Edge) - Ruby 2.7.6 (or v3.1.2) - Node.js 16.18.0 - - Google Chrome 106.0.5249.119 - Firefox 105.0.3 - - etc... - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/1.web_bug_report.yml b/.github/ISSUE_TEMPLATE/1.web_bug_report.yml new file mode 100644 index 0000000000..20e27d103c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.web_bug_report.yml @@ -0,0 +1,76 @@ +name: Bug Report (Web Interface) +description: If you are using Mastodon's web interface and something is not working as expected +labels: [bug, 'status/to triage', 'area/web interface'] +body: + - type: markdown + attributes: + value: | + Make sure that you are submitting a new bug that was not previously reported or already fixed. + + Please use a concise and distinct title for the issue. + - type: textarea + attributes: + label: Steps to reproduce the problem + description: What were you trying to do? + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: input + attributes: + label: Expected behaviour + description: What should have happened? + validations: + required: true + - type: input + attributes: + label: Actual behaviour + description: What happened? + validations: + required: true + - type: textarea + attributes: + label: Detailed description + validations: + required: false + - type: input + attributes: + label: Mastodon instance + description: The address of the Mastodon instance where you experienced the issue + placeholder: mastodon.social + validations: + required: true + - type: input + attributes: + label: Mastodon version + description: | + This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` + placeholder: v4.1.2 + validations: + required: true + - type: input + attributes: + label: Browser name and version + description: | + What browser are you using when getting this bug? Please specify the version as well. + placeholder: Firefox 105.0.3 + validations: + required: true + - type: input + attributes: + label: Operating system + description: | + What OS are you running? Please specify the version as well. + placeholder: macOS 13.4.1 + validations: + required: true + - type: textarea + attributes: + label: Technical details + description: | + Any additional technical details you may have. This can include the full error log, inspector's output… + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml new file mode 100644 index 0000000000..49d5f57209 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml @@ -0,0 +1,65 @@ +name: Bug Report (server / API) +description: | + If something is not working as expected, but is not from using the web interface. +labels: [bug, 'status/to triage'] +body: + - type: markdown + attributes: + value: | + Make sure that you are submitting a new bug that was not previously reported or already fixed. + + Please use a concise and distinct title for the issue. + - type: textarea + attributes: + label: Steps to reproduce the problem + description: What were you trying to do? + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: input + attributes: + label: Expected behaviour + description: What should have happened? + validations: + required: true + - type: input + attributes: + label: Actual behaviour + description: What happened? + validations: + required: true + - type: textarea + attributes: + label: Detailed description + validations: + required: false + - type: input + attributes: + label: Mastodon instance + description: The address of the Mastodon instance where you experienced the issue + placeholder: mastodon.social + validations: + required: false + - type: input + attributes: + label: Mastodon version + description: | + This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` + placeholder: v4.1.2 + validations: + required: false + - type: textarea + attributes: + label: Technical details + description: | + Any additional technical details you may have, like logs or error traces + value: | + If this is happening on your own Mastodon server, please fill out those: + - Ruby version: (from `ruby --version`, eg. v3.1.2) + - Node.js version: (from `node --version`, eg. v18.16.0) + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.feature_request.yml b/.github/ISSUE_TEMPLATE/3.feature_request.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/2.feature_request.yml rename to .github/ISSUE_TEMPLATE/3.feature_request.yml diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1ae40d4161..78530d65b4 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -9,13 +9,14 @@ ], stabilityDays: 3, // Wait 3 days after the package has been published before upgrading it // packageRules order is important, they are applied from top to bottom and are merged, - // so for example grouping rules needs to be at the bottom + // meaning the most important ones must be at the bottom, for example grouping rules + // If we do not want a package to be grouped with others, we need to set its groupName + // to `null` after any other rule set it to something. packageRules: [ { // Ignore major version bumps for these node packages matchManagers: ['npm'], matchPackageNames: [ - '@rails/ujs', // Needs to match the major Rails version 'tesseract.js', // Requires code changes 'react-hotkeys', // Requires code changes @@ -46,17 +47,12 @@ // Ignore major version bumps for these Ruby packages matchManagers: ['bundler'], matchPackageNames: [ + 'rack', // Needs to be synced with Rails version 'sprockets', // Requires manual upgrade https://github.com/rails/sprockets/blob/master/UPGRADING.md#guide-to-upgrading-from-sprockets-3x-to-4x 'strong_migrations', // Requires manual upgrade 'sidekiq', // Requires manual upgrade 'sidekiq-unique-jobs', // Requires manual upgrades and sync with Sidekiq version 'redis', // Requires manual upgrade and sync with Sidekiq version - 'fog-openstack', // TODO: was ignored in https://github.com/mastodon/mastodon/pull/13964 - - // Needs major Rails version bump - 'rack', - 'rails', - 'rails-i18n', ], matchUpdateTypes: ['major'], enabled: false, @@ -91,12 +87,17 @@ // Update devDependencies every week, with one grouped PR matchDepTypes: 'devDependencies', matchUpdateTypes: ['patch', 'minor'], - excludePackageNames: [ - 'typescript', // Typescript has many changes in minor versions, needs to be checked every time - ], groupName: 'devDependencies (non-major)', extends: ['schedule:weekly'], }, + { + // Group all eslint-related packages with `eslint` in the same PR + matchManagers: ['npm'], + matchPackageNames: ['eslint'], + matchPackagePrefixes: ['eslint-', '@typescript-eslint/'], + matchUpdateTypes: ['patch', 'minor'], + groupName: 'eslint (non-major)', + }, { // Update @types/* packages every week, with one grouped PR matchPackagePrefixes: '@types/', @@ -105,6 +106,14 @@ extends: ['schedule:weekly'], addLabels: ['typescript'], }, + { + // We want those packages to always have their own PR + matchManagers: ['npm'], + matchPackageNames: [ + 'typescript', // Typescript has code-impacting changes in minor versions + ], + groupName: null, // We dont want them to belong to any group + }, // Add labels depending on package manager { matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] }, { matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] }, diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml new file mode 100644 index 0000000000..1b15d19885 --- /dev/null +++ b/.github/workflows/build-container-image.yml @@ -0,0 +1,94 @@ +on: + workflow_call: + inputs: + platforms: + required: true + type: string + use_native_arm64_builder: + type: boolean + push_to_images: + type: string + version_suffix: + type: string + flavor: + type: string + tags: + type: string + labels: + type: string + +jobs: + build-image: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: docker/setup-qemu-action@v2 + if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder + + - uses: docker/setup-buildx-action@v2 + id: buildx + if: ${{ !(inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')) }} + + - name: Start a local Docker Builder + if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64') + run: | + docker run --rm -d --name buildkitd -p 1234:1234 --privileged moby/buildkit:latest --addr tcp://0.0.0.0:1234 + + - uses: docker/setup-buildx-action@v2 + id: buildx-native + if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64') + with: + driver: remote + endpoint: tcp://localhost:1234 + platforms: linux/amd64 + append: | + - endpoint: tcp://${{ vars.DOCKER_BUILDER_HETZNER_ARM64_01_HOST }}:13865 + platforms: linux/arm64 + name: mastodon-docker-builder-arm64-01 + driver-opts: + - servername=mastodon-docker-builder-arm64-01 + env: + BUILDER_NODE_1_AUTH_TLS_CACERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CACERT }} + BUILDER_NODE_1_AUTH_TLS_CERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CERT }} + BUILDER_NODE_1_AUTH_TLS_KEY: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_KEY }} + + - name: Log in to Docker Hub + if: contains(inputs.push_to_images, 'tootsuite') + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to the Github Container registry + if: contains(inputs.push_to_images, 'ghcr.io') + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/metadata-action@v4 + id: meta + if: ${{ inputs.push_to_images != '' }} + with: + images: ${{ inputs.push_to_images }} + # Only tag with latest when ran against the latest stable branch + # This needs to be updated after each minor version release + flavor: ${{ inputs.flavor }} + tags: ${{ inputs.tags }} + labels: ${{ inputs.labels }} + + - uses: docker/build-push-action@v4 + with: + context: . + build-args: MASTODON_VERSION_SUFFIX=${{ inputs.version_suffix }} + platforms: ${{ inputs.platforms }} + provenance: false + builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }} + push: ${{ inputs.push_to_images != '' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml deleted file mode 100644 index da4203e357..0000000000 --- a/.github/workflows/build-image.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Build container image -on: - workflow_dispatch: - push: - branches: - - 'main' - pull_request: - paths: - - .github/workflows/build-image.yml - - Dockerfile -permissions: - contents: read - packages: write - -jobs: - build-image: - runs-on: ubuntu-latest - - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - uses: actions/checkout@v3 - - uses: hadolint/hadolint-action@v3.1.0 - - uses: docker/setup-qemu-action@v2 - - uses: docker/setup-buildx-action@v2 - - - name: Log in to the Github Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - if: github.event_name != 'pull_request' - - - uses: docker/metadata-action@v4 - id: meta - with: - images: ghcr.io/${{ github.repository_owner }}/mastodon - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=edge,branch=main - type=sha,prefix=,format=long - - - name: Generate version suffix - id: version_vars - if: github.repository == 'mastodon/mastodon' && github.event_name == 'push' && github.ref_name == 'main' - run: | - echo mastodon_version_suffix=+edge-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT - - - uses: docker/build-push-action@v4 - with: - context: . - build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }} - platforms: linux/amd64,linux/arm64 - provenance: false - builder: ${{ steps.buildx.outputs.name }} - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index f07f7447ca..fe3dc5b187 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -3,58 +3,39 @@ on: workflow_dispatch: schedule: - cron: '0 2 * * *' # run at 2 AM UTC + permissions: contents: read packages: write jobs: - build-nightly-image: + compute-suffix: runs-on: ubuntu-latest - - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - steps: - - uses: actions/checkout@v3 - - uses: hadolint/hadolint-action@v3.1.0 - - uses: docker/setup-qemu-action@v2 - - uses: docker/setup-buildx-action@v2 - - - name: Log in to the Github Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: docker/metadata-action@v4 - id: meta - with: - images: | - ghcr.io/mastodon/mastodon - flavor: | - latest=auto - tags: | - type=raw,value=nightly - type=schedule,pattern=nightly-{{date 'YYYY-MM-DD' tz='Etc/UTC'}} - labels: | - org.opencontainers.image.description=Nightly build image used for testing purposes - - - name: Generate version suffix - id: version_vars + - id: version_vars + env: + TZ: Etc/UTC run: | - echo mastodon_version_suffix=+nightly-$(date +'%Y%m%d') >> $GITHUB_OUTPUT + echo mastodon_version_suffix=nightly-$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT + outputs: + suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }} - - uses: docker/build-push-action@v4 - with: - context: . - build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }} - platforms: linux/amd64,linux/arm64 - provenance: false - builder: ${{ steps.buildx.outputs.name }} - push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + build-image: + needs: compute-suffix + uses: ./.github/workflows/build-container-image.yml + with: + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: false + push_to_images: | + ghcr.io/${{ github.repository_owner }}/mastodon + # The `+` is important here, result will be v4.1.2+nightly-2022-03-05 + version_suffix: +${{ needs.compute-suffix.outputs.suffix }} + labels: | + org.opencontainers.image.description=Nightly build image used for testing purposes + flavor: | + latest=auto + tags: | + type=raw,value=edge + type=raw,value=nightly + type=schedule,pattern=${{ needs.compute-suffix.outputs.suffix }} + secrets: inherit diff --git a/.github/workflows/build-push-pr.yml b/.github/workflows/build-push-pr.yml new file mode 100644 index 0000000000..f78e5ebada --- /dev/null +++ b/.github/workflows/build-push-pr.yml @@ -0,0 +1,41 @@ +name: Build container image for PR +on: + pull_request: + types: [labeled, synchronize, reopened, ready_for_review, opened] + +permissions: + contents: read + packages: write + +jobs: + compute-suffix: + runs-on: ubuntu-latest + # This is only allowed to run if: + # - the PR branch is in the `mastodon/mastodon` repository + # - the PR is not a draft + # - the PR has the "build-image" label + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'build-image') }} + steps: + # Repository needs to be cloned so `git rev-parse` below works + - name: Clone repository + uses: actions/checkout@v3 + - id: version_vars + run: | + echo mastodon_version_suffix=+pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT + outputs: + suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }} + + build-image: + needs: compute-suffix + uses: ./.github/workflows/build-container-image.yml + with: + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: false + push_to_images: | + ghcr.io/${{ github.repository_owner }}/mastodon + version_suffix: ${{ needs.compute-suffix.outputs.suffix }} + flavor: | + latest=auto + tags: | + type=ref,event=pr + secrets: inherit diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml new file mode 100644 index 0000000000..fa923e9606 --- /dev/null +++ b/.github/workflows/build-releases.yml @@ -0,0 +1,24 @@ +name: Build container release images +on: + push: + tags: + - '*' + +permissions: + contents: read + packages: write + +jobs: + build-image: + uses: ./.github/workflows/build-container-image.yml + with: + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: false + push_to_images: | + ghcr.io/${{ github.repository_owner }}/mastodon + flavor: | + latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }} + tags: | + type=pep440,pattern={{raw}} + type=pep440,pattern=v{{major}}.{{minor}} + secrets: inherit diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml new file mode 100644 index 0000000000..6c4869f12d --- /dev/null +++ b/.github/workflows/bundler-audit.yml @@ -0,0 +1,40 @@ +name: Bundler Audit +on: + push: + branches-ignore: + - 'dependabot/**' + paths: + - 'Gemfile*' + - '.ruby-version' + - '.bundler-audit.yml' + - '.github/workflows/bundler-audit.yml' + + pull_request: + paths: + - 'Gemfile*' + - '.ruby-version' + - '.bundler-audit.yml' + - '.github/workflows/bundler-audit.yml' + + schedule: + - cron: '0 5 * * 1' + +jobs: + security: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run bundler-audit + run: bundle exec bundler-audit diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml new file mode 100644 index 0000000000..de35c6f96d --- /dev/null +++ b/.github/workflows/crowdin-download.yml @@ -0,0 +1,77 @@ +name: Crowdin / Download translations +on: + schedule: + - cron: '17 4 * * *' # Every day + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + download-translations: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Increase Git http.postBuffer + # This is needed due to a bug in Ubuntu's cURL version? + # See https://github.com/orgs/community/discussions/55820 + run: | + git config --global http.version HTTP/1.1 + git config --global http.postBuffer 157286400 + + # Download the translation files from Crowdin + - name: crowdin action + uses: crowdin/github-action@v1 + with: + config: crowdin-glitch.yml + upload_sources: false + upload_translations: false + download_translations: true + crowdin_branch_name: main + push_translations: false + create_pull_request: false + env: + CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + # As the files are extracted from a Docker container, they belong to root:root + # We need to fix this before the next steps + - name: Fix file permissions + run: sudo chown -R runner:docker . + + # This is needed to run the normalize step + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run i18n normalize task + run: bundle exec i18n-tasks normalize + + # Create or update the pull request + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5.0.2 + with: + commit-message: 'New Crowdin translations' + title: 'New Crowdin Translations (automated)' + author: 'GitHub Actions ' + body: | + New Crowdin translations, automated with Github Actions + + See `.github/workflows/crowdin-download.yml` + + This PR will be updated every day with new translations. + + Due to a limitation in Github Actions, checks are not running on this PR without manual action. + If you want to run the checks, then close and re-open it. + branch: i18n/crowdin/translations + base: main + labels: i18n diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml new file mode 100644 index 0000000000..f1b62d4daf --- /dev/null +++ b/.github/workflows/crowdin-upload.yml @@ -0,0 +1,36 @@ +name: Crowdin / Upload translations + +on: + push: + branches: + - main + paths: + - crowdin.yml + - app/javascript/mastodon/locales/en.json + - config/locales/en.yml + - config/locales/simple_form.en.yml + - config/locales/activerecord.en.yml + - config/locales/devise.en.yml + - config/locales/doorkeeper.en.yml + - .github/workflows/crowdin-upload.yml + +jobs: + upload-translations: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: crowdin action + uses: crowdin/github-action@v1 + with: + config: crowdin-glitch.yml + upload_sources: true + upload_translations: false + download_translations: false + crowdin_branch_name: main + + env: + CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 0395c8639f..c898b26325 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -8,7 +8,7 @@ on: - 'Gemfile*' - '.rubocop*.yml' - '.ruby-version' - - '.bundler-audit.yml' + - 'config/brakeman.ignore' - '**/*.rb' - '**/*.rake' - '.github/workflows/lint-ruby.yml' @@ -18,7 +18,7 @@ on: - 'Gemfile*' - '.rubocop*.yml' - '.ruby-version' - - '.bundler-audit.yml' + - 'config/brakeman.ignore' - '**/*.rb' - '**/*.rake' - '.github/workflows/lint-ruby.yml' @@ -46,5 +46,6 @@ jobs: - name: Run rubocop run: bundle exec rubocop - - name: Run bundler-audit - run: bundle exec bundler-audit + - name: Run brakeman + if: always() # Run both checks, even if the first failed + run: bundle exec brakeman diff --git a/.github/workflows/rebase-needed.yml b/.github/workflows/rebase-needed.yml index 131a62a576..06d835c090 100644 --- a/.github/workflows/rebase-needed.yml +++ b/.github/workflows/rebase-needed.yml @@ -1,17 +1,8 @@ name: PR Needs Rebase on: - push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' - - 'l10n_main' - pull_request_target: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' - - 'l10n_main' - types: [synchronize] + schedule: + - cron: '0 * * * *' permissions: pull-requests: write @@ -32,5 +23,5 @@ jobs: repoToken: '${{ secrets.GITHUB_TOKEN }}' commentOnClean: This pull request has resolved merge conflicts and is ready for review. commentOnDirty: This pull request has merge conflicts that must be resolved before it can be merged. - retryMax: 10 + retryMax: 30 continueOnMissingPermissions: false diff --git a/.github/workflows/test-image-build.yml b/.github/workflows/test-image-build.yml new file mode 100644 index 0000000000..778e341771 --- /dev/null +++ b/.github/workflows/test-image-build.yml @@ -0,0 +1,21 @@ +name: Test container image build +on: + pull_request: + paths: + - .github/workflows/build-nightly.yml + - .github/workflows/build-push-pr.yml + - .github/workflows/build-releases.yml + - .github/workflows/test-image-build.yml + - Dockerfile +permissions: + contents: read + +jobs: + build-image: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + uses: ./.github/workflows/build-container-image.yml + with: + platforms: linux/amd64 # Testing only on native platform so it is performant diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 07cb1d41f8..ff135867f9 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -107,6 +107,10 @@ jobs: PAM_ENABLED: true PAM_DEFAULT_SERVICE: pam_test PAM_CONTROLLED_SERVICE: pam_test_controlled + OIDC_ENABLED: true + OIDC_SCOPE: read + SAML_ENABLED: true + CAS_ENABLED: true BUNDLE_WITH: 'pam_authentication test' CI_JOBS: ${{ matrix.ci_job }}/4 @@ -149,3 +153,100 @@ jobs: run: './bin/rails db:create db:schema:load db:seed' - run: bundle exec rake rspec_chunked + + test-e2e: + name: End to End testing + runs-on: ubuntu-latest + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_WITH: test + + strategy: + fail-fast: false + matrix: + ruby-version: + - '3.0' + - '3.1' + - '.ruby-version' + + steps: + - uses: actions/checkout@v3 + + - uses: actions/download-artifact@v3 + with: + path: './public' + name: ${{ github.sha }} + + - name: Update package index + run: sudo apt-get update + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Install additional system dependencies + run: sudo apt-get install -y ffmpeg imagemagick + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version}} + bundler-cache: true + + - run: yarn --frozen-lockfile + + - name: Load database schema + run: './bin/rails db:create db:schema:load db:seed' + + - run: bundle exec rake spec:system + + - name: Archive logs + uses: actions/upload-artifact@v3 + if: failure() + with: + name: e2e-logs-${{ matrix.ruby-version }} + path: log/ + + - name: Archive test screenshots + uses: actions/upload-artifact@v3 + if: failure() + with: + name: e2e-screenshots + path: tmp/screenshots/ diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index c601683907..6d2aa0641f 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,73 +1,23 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-03-15 00:55:01 -0400 using Haml-Lint version 0.45.0. +# on 2023-07-20 09:47:50 -0400 using Haml-Lint version 0.48.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 63 - RuboCop: - exclude: - - 'app/views/accounts/_og.html.haml' - - 'app/views/admin/account_warnings/_account_warning.html.haml' - - 'app/views/admin/accounts/index.html.haml' - - 'app/views/admin/accounts/show.html.haml' - - 'app/views/admin/announcements/edit.html.haml' - - 'app/views/admin/announcements/new.html.haml' - - 'app/views/admin/disputes/appeals/_appeal.html.haml' - - 'app/views/admin/domain_blocks/edit.html.haml' - - 'app/views/admin/domain_blocks/new.html.haml' - - 'app/views/admin/ip_blocks/new.html.haml' - - 'app/views/admin/reports/actions/preview.html.haml' - - 'app/views/admin/reports/index.html.haml' - - 'app/views/admin/reports/show.html.haml' - - 'app/views/admin/roles/_form.html.haml' - - 'app/views/admin/settings/about/show.html.haml' - - 'app/views/admin/settings/appearance/show.html.haml' - - 'app/views/admin/settings/registrations/show.html.haml' - - 'app/views/admin/statuses/show.html.haml' - - 'app/views/auth/registrations/new.html.haml' - - 'app/views/disputes/strikes/show.html.haml' - - 'app/views/filters/_filter_fields.html.haml' - - 'app/views/invites/_form.html.haml' - - 'app/views/layouts/application.html.haml' - - 'app/views/layouts/error.html.haml' - - 'app/views/notification_mailer/_status.html.haml' - - 'app/views/settings/applications/_fields.html.haml' - - 'app/views/settings/imports/show.html.haml' - - 'app/views/settings/preferences/appearance/show.html.haml' - - 'app/views/settings/preferences/other/show.html.haml' - - 'app/views/statuses/_detailed_status.html.haml' - - 'app/views/statuses/_poll.html.haml' - - 'app/views/statuses/show.html.haml' - - 'app/views/statuses_cleanup/show.html.haml' - - 'app/views/user_mailer/warning.html.haml' - - # Offense count: 913 + # Offense count: 951 LineLength: enabled: false # Offense count: 22 UnnecessaryStringOutput: - exclude: - - 'app/views/accounts/show.html.haml' - - 'app/views/admin/custom_emojis/_custom_emoji.html.haml' - - 'app/views/admin/relays/_relay.html.haml' - - 'app/views/admin/rules/_rule.html.haml' - - 'app/views/admin/statuses/index.html.haml' - - 'app/views/auth/registrations/_sessions.html.haml' - - 'app/views/disputes/strikes/show.html.haml' - - 'app/views/notification_mailer/_status.html.haml' - - 'app/views/settings/two_factor_authentication_methods/index.html.haml' - - 'app/views/statuses/_detailed_status.html.haml' - - 'app/views/statuses/_poll.html.haml' - - 'app/views/statuses/_simple_status.html.haml' - - 'app/views/user_mailer/suspicious_sign_in.html.haml' - - 'app/views/user_mailer/webauthn_credential_added.html.haml' - - 'app/views/user_mailer/webauthn_credential_deleted.html.haml' - - 'app/views/user_mailer/welcome.html.haml' + enabled: false + + # Offense count: 57 + RuboCop: + enabled: false # Offense count: 3 ViewLength: @@ -76,27 +26,18 @@ linters: - 'app/views/admin/reports/show.html.haml' - 'app/views/disputes/strikes/show.html.haml' - # Offense count: 41 + # Offense count: 32 InstanceVariables: exclude: - 'app/views/admin/reports/_actions.html.haml' - 'app/views/admin/roles/_form.html.haml' - 'app/views/admin/webhooks/_form.html.haml' - - 'app/views/auth/registrations/_sessions.html.haml' - 'app/views/auth/registrations/_status.html.haml' - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' - 'app/views/authorize_interactions/_post_follow_actions.html.haml' - 'app/views/invites/_form.html.haml' - 'app/views/relationships/_account.html.haml' - 'app/views/shared/_og.html.haml' - - 'app/views/statuses/_status.html.haml' - - # Offense count: 6 - ConsecutiveSilentScripts: - exclude: - - 'app/views/admin/settings/shared/_links.html.haml' - - 'app/views/settings/login_activities/_login_activity.html.haml' - - 'app/views/statuses/_poll.html.haml' # Offense count: 3 IdNames: diff --git a/.rubocop.yml b/.rubocop.yml index eff89bdaee..c8a433c729 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,7 +24,6 @@ AllCops: Exclude: - db/schema.rb - 'bin/*' - - 'Rakefile' - 'node_modules/**/*' - 'Vagrantfile' - 'vendor/**/*' @@ -39,14 +38,7 @@ Layout/FirstHashElementIndentation: # Reason: Currently disabled in .rubocop_todo.yml # https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength Layout/LineLength: - AllowedPatterns: - # Allow comments to be long lines - - !ruby/regexp / \# .*$/ - - !ruby/regexp /^\# .*$/ - Exclude: - - 'lib/mastodon/cli/*.rb' - - db/*migrate/**/* - - db/seeds/**/* + Max: 320 # Default of 120 causes a duplicate entry in generated todo file # Reason: # https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier @@ -132,12 +124,6 @@ RSpec/FilePath: Exclude: - 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder - 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder - - 'spec/controllers/concerns/account_controller_concern_spec.rb' # Concerns describe ApplicationController and don't fit naming - - 'spec/controllers/concerns/export_controller_concern_spec.rb' - - 'spec/controllers/concerns/localized_spec.rb' - - 'spec/controllers/concerns/rate_limit_headers_spec.rb' - - 'spec/controllers/concerns/signature_verification_spec.rb' - - 'spec/controllers/concerns/user_tracking_concern_spec.rb' # Reason: # https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject @@ -192,6 +178,11 @@ Style/RedundantBegin: Style/RescueStandardError: EnforcedStyle: implicit +# Reason: Simplify some spec layouts +# https://docs.rubocop.org/rubocop/cops_style.html#stylesemicolon +Style/Semicolon: + AllowAsExpressionSeparator: true + # Reason: Originally disabled for CodeClimate, and no config consensus has been found # https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray Style/SymbolArray: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c1d580e515..bd1758f783 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.52.1. +# using RuboCop version 1.54.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -28,7 +28,6 @@ Layout/ArgumentAlignment: # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit Layout/HashAlignment: Exclude: - - 'config/boot.rb' - 'config/environments/production.rb' - 'config/initializers/rack_attack.rb' - 'config/routes.rb' @@ -40,6 +39,13 @@ Layout/LeadingCommentSpace: - 'config/application.rb' - 'config/initializers/omniauth.rb' +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'app/models/account.rb' + # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_no_space, require_space @@ -48,15 +54,6 @@ Layout/SpaceInLambdaLiteral: - 'config/environments/production.rb' - 'config/initializers/content_security_policy.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns. -Lint/AmbiguousBlockAssociation: - Exclude: - - 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb' - - 'spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb' - - 'spec/services/activitypub/process_status_update_service_spec.rb' - - 'spec/services/post_status_service_spec.rb' - # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -106,11 +103,6 @@ Lint/OrAssignmentToConstant: Exclude: - 'lib/sanitize_ext/sanitize_config.rb' -# This cop supports safe autocorrection (--autocorrect). -Lint/SendWithMixinArgument: - Exclude: - - 'config/application.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -127,7 +119,6 @@ Lint/UselessAssignment: - 'config/initializers/omniauth.rb' - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb' - - 'spec/controllers/api/v1/bookmarks_controller_spec.rb' - 'spec/controllers/api/v1/favourites_controller_spec.rb' - 'spec/controllers/concerns/account_controller_concern_spec.rb' - 'spec/helpers/jsonld_helper_spec.rb' @@ -142,15 +133,9 @@ Lint/UselessAssignment: - 'spec/services/resolve_url_service_spec.rb' - 'spec/views/statuses/show.html.haml_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: CheckForMethodsWithNoSideEffects. -Lint/Void: - Exclude: - - 'spec/services/resolve_account_service_spec.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 150 + Max: 143 Exclude: - 'app/serializers/initial_state_serializer.rb' @@ -167,30 +152,6 @@ Metrics/CyclomaticComplexity: Metrics/PerceivedComplexity: Max: 27 -Naming/AccessorMethodName: - Exclude: - - 'app/controllers/auth/sessions_controller.rb' - -# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms. -# CheckDefinitionPathHierarchyRoots: lib, spec, test, src -# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS -Naming/FileName: - Exclude: - - 'config/locales/sr-Latn.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyleForLeadingUnderscores. -# SupportedStylesForLeadingUnderscores: disallowed, required, optional -Naming/MemoizedInstanceVariableName: - Exclude: - - 'app/controllers/api/v1/bookmarks_controller.rb' - - 'app/controllers/api/v1/favourites_controller.rb' - - 'app/controllers/concerns/rate_limit_headers.rb' - - 'app/lib/activitypub/activity.rb' - - 'app/services/resolve_url_service.rb' - - 'app/services/search_service.rb' - - 'config/initializers/rack_attack.rb' - # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 @@ -205,14 +166,6 @@ Naming/VariableNumber: - 'spec/models/domain_block_spec.rb' - 'spec/models/user_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Performance/UnfreezeString: - Exclude: - - 'app/lib/rss/builder.rb' - - 'app/lib/text_formatter.rb' - - 'app/validators/status_length_validator.rb' - - 'lib/tasks/mastodon.rake' - RSpec/AnyInstance: Exclude: - 'spec/controllers/activitypub/inboxes_controller_spec.rb' @@ -228,46 +181,10 @@ RSpec/AnyInstance: - 'spec/models/account_spec.rb' - 'spec/models/setting_spec.rb' - 'spec/services/activitypub/process_collection_service_spec.rb' - - 'spec/validators/blacklisted_email_validator_spec.rb' - 'spec/validators/follow_limit_validator_spec.rb' - 'spec/workers/activitypub/delivery_worker_spec.rb' - 'spec/workers/web/push_notification_worker_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/helpers/admin/action_logs_helper_spec.rb' - - 'spec/models/account_alias_spec.rb' - - 'spec/models/account_deletion_request_spec.rb' - - 'spec/models/account_moderation_note_spec.rb' - - 'spec/models/announcement_mute_spec.rb' - - 'spec/models/announcement_reaction_spec.rb' - - 'spec/models/announcement_spec.rb' - - 'spec/models/backup_spec.rb' - - 'spec/models/conversation_mute_spec.rb' - - 'spec/models/custom_filter_keyword_spec.rb' - - 'spec/models/custom_filter_spec.rb' - - 'spec/models/device_spec.rb' - - 'spec/models/encrypted_message_spec.rb' - - 'spec/models/featured_tag_spec.rb' - - 'spec/models/follow_recommendation_suppression_spec.rb' - - 'spec/models/list_account_spec.rb' - - 'spec/models/list_spec.rb' - - 'spec/models/login_activity_spec.rb' - - 'spec/models/mute_spec.rb' - - 'spec/models/preview_card_spec.rb' - - 'spec/models/preview_card_trend_spec.rb' - - 'spec/models/relay_spec.rb' - - 'spec/models/scheduled_status_spec.rb' - - 'spec/models/status_stat_spec.rb' - - 'spec/models/status_trend_spec.rb' - - 'spec/models/system_key_spec.rb' - - 'spec/models/tag_follow_spec.rb' - - 'spec/models/unavailable_domain_spec.rb' - - 'spec/models/user_invite_request_spec.rb' - - 'spec/models/web/setting_spec.rb' - - 'spec/services/unmute_service_spec.rb' - # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 22 @@ -285,7 +202,6 @@ RSpec/HookArgument: - 'spec/serializers/activitypub/note_serializer_spec.rb' - 'spec/serializers/activitypub/update_poll_serializer_spec.rb' - 'spec/services/import_service_spec.rb' - - 'spec/spec_helper.rb' # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: @@ -321,7 +237,6 @@ RSpec/LetSetup: - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb' - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb' - 'spec/controllers/api/v2/filters/statuses_controller_spec.rb' - - 'spec/controllers/api/v2/filters_controller_spec.rb' - 'spec/controllers/auth/confirmations_controller_spec.rb' - 'spec/controllers/auth/passwords_controller_spec.rb' - 'spec/controllers/auth/sessions_controller_spec.rb' @@ -331,6 +246,7 @@ RSpec/LetSetup: - 'spec/controllers/oauth/tokens_controller_spec.rb' - 'spec/controllers/settings/imports_controller_spec.rb' - 'spec/lib/activitypub/activity/delete_spec.rb' + - 'spec/lib/vacuum/applications_vacuum_spec.rb' - 'spec/lib/vacuum/preview_cards_vacuum_spec.rb' - 'spec/models/account_spec.rb' - 'spec/models/account_statuses_cleanup_policy_spec.rb' @@ -368,11 +284,7 @@ RSpec/MessageChain: RSpec/MessageSpies: Exclude: - 'spec/controllers/admin/accounts_controller_spec.rb' - - 'spec/controllers/api/base_controller_spec.rb' - - 'spec/controllers/auth/registrations_controller_spec.rb' - 'spec/helpers/admin/account_moderation_notes_helper_spec.rb' - - 'spec/helpers/application_helper_spec.rb' - - 'spec/lib/status_finder_spec.rb' - 'spec/lib/webfinger_resource_spec.rb' - 'spec/models/admin/account_action_spec.rb' - 'spec/models/concerns/remotable_spec.rb' @@ -400,89 +312,13 @@ RSpec/PendingWithoutReason: Exclude: - 'spec/models/account_spec.rb' -RSpec/StubbedMock: - Exclude: - - 'spec/controllers/api/base_controller_spec.rb' - - 'spec/controllers/api/v1/media_controller_spec.rb' - - 'spec/controllers/auth/registrations_controller_spec.rb' - - 'spec/helpers/application_helper_spec.rb' - - 'spec/lib/status_filter_spec.rb' - - 'spec/lib/status_finder_spec.rb' - - 'spec/lib/webfinger_resource_spec.rb' - - 'spec/services/activitypub/process_collection_service_spec.rb' - -RSpec/SubjectDeclaration: - Exclude: - - 'spec/controllers/admin/domain_blocks_controller_spec.rb' - - 'spec/models/account_migration_spec.rb' - - 'spec/models/account_spec.rb' - - 'spec/models/relationship_filter_spec.rb' - - 'spec/models/user_role_spec.rb' - - 'spec/policies/account_moderation_note_policy_spec.rb' - - 'spec/policies/account_policy_spec.rb' - - 'spec/policies/backup_policy_spec.rb' - - 'spec/policies/custom_emoji_policy_spec.rb' - - 'spec/policies/domain_block_policy_spec.rb' - - 'spec/policies/email_domain_block_policy_spec.rb' - - 'spec/policies/instance_policy_spec.rb' - - 'spec/policies/invite_policy_spec.rb' - - 'spec/policies/relay_policy_spec.rb' - - 'spec/policies/report_note_policy_spec.rb' - - 'spec/policies/report_policy_spec.rb' - - 'spec/policies/settings_policy_spec.rb' - - 'spec/policies/tag_policy_spec.rb' - - 'spec/policies/user_policy_spec.rb' - - 'spec/services/activitypub/process_account_service_spec.rb' - -RSpec/SubjectStub: - Exclude: - - 'spec/services/unallow_domain_service_spec.rb' - - 'spec/validators/blacklisted_email_validator_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Rails/ApplicationController: Exclude: - 'app/controllers/health_controller.rb' -# Configuration parameters: Database, Include. -# SupportedDatabases: mysql, postgresql -# Include: db/migrate/*.rb -Rails/BulkChangeTable: - Exclude: - - 'db/migrate/20160222143943_add_profile_fields_to_accounts.rb' - - 'db/migrate/20160223162837_add_metadata_to_statuses.rb' - - 'db/migrate/20160305115639_add_devise_to_users.rb' - - 'db/migrate/20160314164231_add_owner_to_application.rb' - - 'db/migrate/20160926213048_remove_owner_from_application.rb' - - 'db/migrate/20161003142332_add_confirmable_to_users.rb' - - 'db/migrate/20170112154826_migrate_settings.rb' - - 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb' - - 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb' - - 'db/migrate/20170330021336_add_counter_caches.rb' - - 'db/migrate/20170425202925_add_oembed_to_preview_cards.rb' - - 'db/migrate/20170427011934_re_add_owner_to_application.rb' - - 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb' - - 'db/migrate/20170624134742_add_description_to_session_activations.rb' - - 'db/migrate/20170718211102_add_activitypub_to_accounts.rb' - - 'db/migrate/20171006142024_add_uri_to_custom_emojis.rb' - - 'db/migrate/20180812123222_change_relays_enabled.rb' - - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' - - 'db/migrate/20190805123746_add_capabilities_to_tags.rb' - - 'db/migrate/20190807135426_add_comments_to_domain_blocks.rb' - - 'db/migrate/20190815225426_add_last_status_at_to_tags.rb' - - 'db/migrate/20190901035623_add_max_score_to_tags.rb' - - 'db/migrate/20200417125749_add_storage_schema_version.rb' - - 'db/migrate/20200608113046_add_sign_in_token_to_users.rb' - - 'db/migrate/20211112011713_add_language_to_preview_cards.rb' - - 'db/migrate/20211231080958_add_category_to_reports.rb' - - 'db/migrate/20220202200743_add_trendable_to_accounts.rb' - - 'db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb' - - 'db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb' - - 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb' - - 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb' - # Configuration parameters: Include. -# Include: db/migrate/*.rb +# Include: db/**/*.rb Rails/CreateTableWithTimestamps: Exclude: - 'db/migrate/20170508230434_create_conversation_mutes.rb' @@ -756,7 +592,7 @@ Style/FetchEnvVar: - 'app/lib/translation_service.rb' - 'config/environments/development.rb' - 'config/environments/production.rb' - - 'config/initializers/2_whitelist_mode.rb' + - 'config/initializers/2_limited_federation_mode.rb' - 'config/initializers/blacklists.rb' - 'config/initializers/cache_buster.rb' - 'config/initializers/content_security_policy.rb' @@ -779,406 +615,6 @@ Style/FormatStringToken: - 'config/initializers/devise.rb' - 'lib/paperclip/color_extractor.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, always_true, never -Style/FrozenStringLiteralComment: - Exclude: - - 'app/views/accounts/show.rss.ruby' - - 'app/views/tags/show.rss.ruby' - - 'app/views/well_known/host_meta/show.xml.ruby' - - 'config/application.rb' - - 'config/boot.rb' - - 'config/environment.rb' - - 'config/environments/development.rb' - - 'config/environments/production.rb' - - 'config/environments/test.rb' - - 'config/initializers/0_post_deployment_migrations.rb' - - 'config/initializers/active_model_serializers.rb' - - 'config/initializers/application_controller_renderer.rb' - - 'config/initializers/assets.rb' - - 'config/initializers/backtrace_silencers.rb' - - 'config/initializers/cache_logging.rb' - - 'config/initializers/chewy.rb' - - 'config/initializers/content_security_policy.rb' - - 'config/initializers/cookies_serializer.rb' - - 'config/initializers/cors.rb' - - 'config/initializers/devise.rb' - - 'config/initializers/doorkeeper.rb' - - 'config/initializers/fast_blank.rb' - - 'config/initializers/ffmpeg.rb' - - 'config/initializers/filter_parameter_logging.rb' - - 'config/initializers/http_client_proxy.rb' - - 'config/initializers/httplog.rb' - - 'config/initializers/inflections.rb' - - 'config/initializers/mail_delivery_job.rb' - - 'config/initializers/makara.rb' - - 'config/initializers/mime_types.rb' - - 'config/initializers/oj.rb' - - 'config/initializers/omniauth.rb' - - 'config/initializers/open_uri_redirection.rb' - - 'config/initializers/permissions_policy.rb' - - 'config/initializers/pghero.rb' - - 'config/initializers/preload_link_headers.rb' - - 'config/initializers/premailer_rails.rb' - - 'config/initializers/rack_attack_logging.rb' - - 'config/initializers/redis.rb' - - 'config/initializers/session_store.rb' - - 'config/initializers/simple_form.rb' - - 'config/initializers/stoplight.rb' - - 'config/initializers/trusted_proxies.rb' - - 'config/initializers/twitter_regex.rb' - - 'config/initializers/webauthn.rb' - - 'config/initializers/wrap_parameters.rb' - - 'config/locales/sr-Latn.rb' - - 'config/locales/sr.rb' - - 'config/puma.rb' - - 'db/migrate/20160220174730_create_accounts.rb' - - 'db/migrate/20160220211917_create_statuses.rb' - - 'db/migrate/20160221003140_create_users.rb' - - 'db/migrate/20160221003621_create_follows.rb' - - 'db/migrate/20160222122600_create_stream_entries.rb' - - 'db/migrate/20160222143943_add_profile_fields_to_accounts.rb' - - 'db/migrate/20160223162837_add_metadata_to_statuses.rb' - - 'db/migrate/20160223164502_make_uris_nullable_in_statuses.rb' - - 'db/migrate/20160223165723_add_url_to_statuses.rb' - - 'db/migrate/20160223165855_add_url_to_accounts.rb' - - 'db/migrate/20160223171800_create_favourites.rb' - - 'db/migrate/20160224223247_create_mentions.rb' - - 'db/migrate/20160227230233_add_attachment_avatar_to_accounts.rb' - - 'db/migrate/20160305115639_add_devise_to_users.rb' - - 'db/migrate/20160306172223_create_doorkeeper_tables.rb' - - 'db/migrate/20160312193225_add_attachment_header_to_accounts.rb' - - 'db/migrate/20160314164231_add_owner_to_application.rb' - - 'db/migrate/20160316103650_add_missing_indices.rb' - - 'db/migrate/20160322193748_add_avatar_remote_url_to_accounts.rb' - - 'db/migrate/20160325130944_add_admin_to_users.rb' - - 'db/migrate/20160826155805_add_superapp_to_oauth_applications.rb' - - 'db/migrate/20160905150353_create_media_attachments.rb' - - 'db/migrate/20160919221059_add_subscription_expires_at_to_accounts.rb' - - 'db/migrate/20160920003904_remove_verify_token_from_accounts.rb' - - 'db/migrate/20160926213048_remove_owner_from_application.rb' - - 'db/migrate/20161003142332_add_confirmable_to_users.rb' - - 'db/migrate/20161003145426_create_blocks.rb' - - 'db/migrate/20161006213403_rails_settings_migration.rb' - - 'db/migrate/20161009120834_create_domain_blocks.rb' - - 'db/migrate/20161027172456_add_silenced_to_accounts.rb' - - 'db/migrate/20161104173623_create_tags.rb' - - 'db/migrate/20161105130633_create_statuses_tags_join_table.rb' - - 'db/migrate/20161116162355_add_locale_to_users.rb' - - 'db/migrate/20161119211120_create_notifications.rb' - - 'db/migrate/20161122163057_remove_unneeded_indexes.rb' - - 'db/migrate/20161123093447_add_sensitive_to_statuses.rb' - - 'db/migrate/20161128103007_create_subscriptions.rb' - - 'db/migrate/20161130142058_add_last_successful_delivery_at_to_subscriptions.rb' - - 'db/migrate/20161130185319_add_visibility_to_statuses.rb' - - 'db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb' - - 'db/migrate/20161203164520_add_from_account_id_to_notifications.rb' - - 'db/migrate/20161205214545_add_suspended_to_accounts.rb' - - 'db/migrate/20161221152630_add_hidden_to_stream_entries.rb' - - 'db/migrate/20161222201034_add_locked_to_accounts.rb' - - 'db/migrate/20161222204147_create_follow_requests.rb' - - 'db/migrate/20170105224407_add_shortcode_to_media_attachments.rb' - - 'db/migrate/20170109120109_create_web_settings.rb' - - 'db/migrate/20170112154826_migrate_settings.rb' - - 'db/migrate/20170114194937_add_application_to_statuses.rb' - - 'db/migrate/20170114203041_add_website_to_oauth_application.rb' - - 'db/migrate/20170119214911_create_preview_cards.rb' - - 'db/migrate/20170123162658_add_severity_to_domain_blocks.rb' - - 'db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb' - - 'db/migrate/20170125145934_add_spoiler_text_to_statuses.rb' - - 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb' - - 'db/migrate/20170205175257_remove_devices.rb' - - 'db/migrate/20170209184350_add_reply_to_statuses.rb' - - 'db/migrate/20170214110202_create_reports.rb' - - 'db/migrate/20170217012631_add_reblog_of_id_foreign_key_to_statuses.rb' - - 'db/migrate/20170301222600_create_mutes.rb' - - 'db/migrate/20170303212857_add_last_emailed_at_to_users.rb' - - 'db/migrate/20170304202101_add_type_to_media_attachments.rb' - - 'db/migrate/20170317193015_add_search_index_to_accounts.rb' - - 'db/migrate/20170318214217_add_header_remote_url_to_accounts.rb' - - 'db/migrate/20170322021028_add_lowercase_index_to_accounts.rb' - - 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb' - - 'db/migrate/20170322162804_add_search_index_to_tags.rb' - - 'db/migrate/20170330021336_add_counter_caches.rb' - - 'db/migrate/20170330163835_create_imports.rb' - - 'db/migrate/20170330164118_add_attachment_data_to_imports.rb' - - 'db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb' - - 'db/migrate/20170405112956_add_index_on_mentions_status_id.rb' - - 'db/migrate/20170406215816_add_notifications_and_favourites_indices.rb' - - 'db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb' - - 'db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb' - - 'db/migrate/20170414132105_add_language_to_statuses.rb' - - 'db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb' - - 'db/migrate/20170423005413_add_allowed_languages_to_user.rb' - - 'db/migrate/20170424003227_create_account_domain_blocks.rb' - - 'db/migrate/20170424112722_add_status_id_index_to_statuses_tags.rb' - - 'db/migrate/20170425131920_add_media_attachment_meta.rb' - - 'db/migrate/20170425202925_add_oembed_to_preview_cards.rb' - - 'db/migrate/20170427011934_re_add_owner_to_application.rb' - - 'db/migrate/20170506235850_create_conversations.rb' - - 'db/migrate/20170507000211_add_conversation_id_to_statuses.rb' - - 'db/migrate/20170507141759_optimize_index_subscriptions.rb' - - 'db/migrate/20170508230434_create_conversation_mutes.rb' - - 'db/migrate/20170516072309_add_index_accounts_on_uri.rb' - - 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb' - - 'db/migrate/20170601210557_add_index_on_media_attachments_account_id.rb' - - 'db/migrate/20170604144747_add_foreign_keys_for_accounts.rb' - - 'db/migrate/20170606113804_change_tag_search_index_to_btree.rb' - - 'db/migrate/20170609145826_remove_default_language_from_statuses.rb' - - 'db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb' - - 'db/migrate/20170623152212_create_session_activations.rb' - - 'db/migrate/20170624134742_add_description_to_session_activations.rb' - - 'db/migrate/20170625140443_add_access_token_id_to_session_activations.rb' - - 'db/migrate/20170711225116_fix_null_booleans.rb' - - 'db/migrate/20170713112503_make_tag_search_case_insensitive.rb' - - 'db/migrate/20170713175513_create_web_push_subscriptions.rb' - - 'db/migrate/20170713190709_add_web_push_subscription_to_session_activations.rb' - - 'db/migrate/20170714184731_add_domain_to_subscriptions.rb' - - 'db/migrate/20170716191202_add_hide_notifications_to_mute.rb' - - 'db/migrate/20170718211102_add_activitypub_to_accounts.rb' - - 'db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb' - - 'db/migrate/20170823162448_create_status_pins.rb' - - 'db/migrate/20170824103029_add_timestamps_to_status_pins.rb' - - 'db/migrate/20170829215220_remove_status_pins_account_index.rb' - - 'db/migrate/20170901141119_truncate_preview_cards.rb' - - 'db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb' - - 'db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb' - - 'db/migrate/20170905165803_add_local_to_statuses.rb' - - 'db/migrate/20170913000752_create_site_uploads.rb' - - 'db/migrate/20170917153509_create_custom_emojis.rb' - - 'db/migrate/20170918125918_ids_to_bigints.rb' - - 'db/migrate/20170920024819_status_ids_to_timestamp_ids.rb' - - 'db/migrate/20170920032311_fix_reblogs_in_feeds.rb' - - 'db/migrate/20170924022025_ids_to_bigints2.rb' - - 'db/migrate/20170927215609_add_description_to_media_attachments.rb' - - 'db/migrate/20170928082043_create_email_domain_blocks.rb' - - 'db/migrate/20171005102658_create_account_moderation_notes.rb' - - 'db/migrate/20171005171936_add_disabled_to_custom_emojis.rb' - - 'db/migrate/20171006142024_add_uri_to_custom_emojis.rb' - - 'db/migrate/20171010023049_add_foreign_key_to_account_moderation_notes.rb' - - 'db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb' - - 'db/migrate/20171020084748_add_visible_in_picker_to_custom_emoji.rb' - - 'db/migrate/20171028221157_add_reblogs_to_follows.rb' - - 'db/migrate/20171107143332_add_memorial_to_accounts.rb' - - 'db/migrate/20171107143624_add_disabled_to_users.rb' - - 'db/migrate/20171109012327_add_moderator_to_accounts.rb' - - 'db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb' - - 'db/migrate/20171114231651_create_lists.rb' - - 'db/migrate/20171116161857_create_list_accounts.rb' - - 'db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb' - - 'db/migrate/20171119172437_create_admin_action_logs.rb' - - 'db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb' - - 'db/migrate/20171125024930_create_invites.rb' - - 'db/migrate/20171125031751_add_invite_id_to_users.rb' - - 'db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb' - - 'db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb' - - 'db/migrate/20171129172043_add_index_on_stream_entries.rb' - - 'db/migrate/20171130000000_add_embed_url_to_preview_cards.rb' - - 'db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb' - - 'db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb' - - 'db/migrate/20171226094803_more_faster_index_on_notifications.rb' - - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb' - - 'db/migrate/20180109143959_add_remember_token_to_users.rb' - - 'db/migrate/20180204034416_create_identities.rb' - - 'db/migrate/20180206000000_change_user_id_nonnullable.rb' - - 'db/migrate/20180211015820_create_backups.rb' - - 'db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb' - - 'db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb' - - 'db/migrate/20180402031200_add_assigned_account_id_to_reports.rb' - - 'db/migrate/20180402040909_create_report_notes.rb' - - 'db/migrate/20180410204633_add_fields_to_accounts.rb' - - 'db/migrate/20180416210259_add_uri_to_relationships.rb' - - 'db/migrate/20180506221944_add_actor_type_to_accounts.rb' - - 'db/migrate/20180510214435_add_access_token_id_to_web_push_subscriptions.rb' - - 'db/migrate/20180510230049_migrate_web_push_subscriptions.rb' - - 'db/migrate/20180528141303_fix_accounts_unique_index.rb' - - 'db/migrate/20180608213548_reject_following_blocked_users.rb' - - 'db/migrate/20180609104432_migrate_web_push_subscriptions2.rb' - - 'db/migrate/20180615122121_add_autofollow_to_invites.rb' - - 'db/migrate/20180616192031_add_chosen_languages_to_users.rb' - - 'db/migrate/20180617162849_remove_unused_indexes.rb' - - 'db/migrate/20180628181026_create_custom_filters.rb' - - 'db/migrate/20180707154237_add_whole_word_to_custom_filter.rb' - - 'db/migrate/20180711152640_create_relays.rb' - - 'db/migrate/20180808175627_create_account_pins.rb' - - 'db/migrate/20180812123222_change_relays_enabled.rb' - - 'db/migrate/20180812162710_create_status_stats.rb' - - 'db/migrate/20180812173710_copy_status_stats.rb' - - 'db/migrate/20180814171349_add_confidential_to_doorkeeper_application.rb' - - 'db/migrate/20180831171112_create_bookmarks.rb' - - 'db/migrate/20180929222014_create_account_conversations.rb' - - 'db/migrate/20181007025445_create_pghero_space_stats.rb' - - 'db/migrate/20181010141500_add_silent_to_mentions.rb' - - 'db/migrate/20181017170937_add_reject_reports_to_domain_blocks.rb' - - 'db/migrate/20181018205649_add_unread_to_account_conversations.rb' - - 'db/migrate/20181024224956_migrate_account_conversations.rb' - - 'db/migrate/20181026034033_remove_faux_remote_account_duplicates.rb' - - 'db/migrate/20181116165755_create_account_stats.rb' - - 'db/migrate/20181116173541_copy_account_stats.rb' - - 'db/migrate/20181127130500_identity_id_to_bigint.rb' - - 'db/migrate/20181127165847_add_show_replies_to_lists.rb' - - 'db/migrate/20181203003808_create_accounts_tags_join_table.rb' - - 'db/migrate/20181203021853_add_discoverable_to_accounts.rb' - - 'db/migrate/20181204193439_add_last_status_at_to_account_stats.rb' - - 'db/migrate/20181204215309_create_account_tag_stats.rb' - - 'db/migrate/20181207011115_downcase_custom_emoji_domains.rb' - - 'db/migrate/20181213184704_create_account_warnings.rb' - - 'db/migrate/20181213185533_create_account_warning_presets.rb' - - 'db/migrate/20181219235220_add_created_by_application_id_to_users.rb' - - 'db/migrate/20181226021420_add_also_known_as_to_accounts.rb' - - 'db/migrate/20190103124649_create_scheduled_statuses.rb' - - 'db/migrate/20190103124754_add_scheduled_status_id_to_media_attachments.rb' - - 'db/migrate/20190117114553_create_tombstones.rb' - - 'db/migrate/20190201012802_add_overwrite_to_imports.rb' - - 'db/migrate/20190203180359_create_featured_tags.rb' - - 'db/migrate/20190225031541_create_polls.rb' - - 'db/migrate/20190225031625_create_poll_votes.rb' - - 'db/migrate/20190226003449_add_poll_id_to_statuses.rb' - - 'db/migrate/20190304152020_add_uri_to_poll_votes.rb' - - 'db/migrate/20190306145741_add_lock_version_to_polls.rb' - - 'db/migrate/20190307234537_add_approved_to_users.rb' - - 'db/migrate/20190314181829_migrate_open_registrations_setting.rb' - - 'db/migrate/20190316190352_create_account_identity_proofs.rb' - - 'db/migrate/20190317135723_add_uri_to_reports.rb' - - 'db/migrate/20190403141604_add_comment_to_invites.rb' - - 'db/migrate/20190409054914_create_user_invite_requests.rb' - - 'db/migrate/20190420025523_add_blurhash_to_media_attachments.rb' - - 'db/migrate/20190509164208_add_by_moderator_to_tombstone.rb' - - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' - - 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb' - - 'db/migrate/20190627222225_create_custom_emoji_categories.rb' - - 'db/migrate/20190627222826_add_category_id_to_custom_emojis.rb' - - 'db/migrate/20190701022101_add_trust_level_to_accounts.rb' - - 'db/migrate/20190705002136_create_domain_allows.rb' - - 'db/migrate/20190715164535_add_instance_actor.rb' - - 'db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb' - - 'db/migrate/20190729185330_add_score_to_tags.rb' - - 'db/migrate/20190805123746_add_capabilities_to_tags.rb' - - 'db/migrate/20190807135426_add_comments_to_domain_blocks.rb' - - 'db/migrate/20190815225426_add_last_status_at_to_tags.rb' - - 'db/migrate/20190819134503_add_deleted_at_to_statuses.rb' - - 'db/migrate/20190820003045_update_statuses_index.rb' - - 'db/migrate/20190823221802_add_local_index_to_statuses.rb' - - 'db/migrate/20190901035623_add_max_score_to_tags.rb' - - 'db/migrate/20190904222339_create_markers.rb' - - 'db/migrate/20190914202517_create_account_migrations.rb' - - 'db/migrate/20190915194355_create_account_aliases.rb' - - 'db/migrate/20190927232842_add_voters_count_to_polls.rb' - - 'db/migrate/20191001213028_add_lock_version_to_account_stats.rb' - - 'db/migrate/20191007013357_update_pt_locales.rb' - - 'db/migrate/20191031163205_change_list_account_follow_nullable.rb' - - 'db/migrate/20191212003415_increase_backup_size.rb' - - 'db/migrate/20191212163405_add_hide_collections_to_accounts.rb' - - 'db/migrate/20191218153258_create_announcements.rb' - - 'db/migrate/20200113125135_create_announcement_mutes.rb' - - 'db/migrate/20200114113335_create_announcement_reactions.rb' - - 'db/migrate/20200119112504_add_public_index_to_statuses.rb' - - 'db/migrate/20200126203551_add_published_at_to_announcements.rb' - - 'db/migrate/20200306035625_add_processing_to_media_attachments.rb' - - 'db/migrate/20200309150742_add_forwarded_to_reports.rb' - - 'db/migrate/20200312144258_add_title_to_account_warning_presets.rb' - - 'db/migrate/20200312162302_add_status_ids_to_announcements.rb' - - 'db/migrate/20200312185443_add_parent_id_to_email_domain_blocks.rb' - - 'db/migrate/20200317021758_add_expires_at_to_mutes.rb' - - 'db/migrate/20200407201300_create_unavailable_domains.rb' - - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb' - - 'db/migrate/20200417125749_add_storage_schema_version.rb' - - 'db/migrate/20200508212852_reset_unique_jobs_locks.rb' - - 'db/migrate/20200510110808_reset_web_app_secret.rb' - - 'db/migrate/20200510181721_remove_duplicated_indexes_pghero.rb' - - 'db/migrate/20200516180352_create_devices.rb' - - 'db/migrate/20200516183822_create_one_time_keys.rb' - - 'db/migrate/20200518083523_create_encrypted_messages.rb' - - 'db/migrate/20200521180606_encrypted_message_ids_to_timestamp_ids.rb' - - 'db/migrate/20200529214050_add_devices_url_to_accounts.rb' - - 'db/migrate/20200601222558_create_system_keys.rb' - - 'db/migrate/20200605155027_add_blurhash_to_preview_cards.rb' - - 'db/migrate/20200608113046_add_sign_in_token_to_users.rb' - - 'db/migrate/20200614002136_add_sensitized_to_accounts.rb' - - 'db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb' - - 'db/migrate/20200622213645_media_attachment_ids_to_timestamp_ids.rb' - - 'db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb' - - 'db/migrate/20200628133322_create_account_notes.rb' - - 'db/migrate/20200630190240_create_webauthn_credentials.rb' - - 'db/migrate/20200630190544_add_webauthn_id_to_users.rb' - - 'db/migrate/20200908193330_create_account_deletion_requests.rb' - - 'db/migrate/20200917192924_add_notify_to_follows.rb' - - 'db/migrate/20200917193034_add_type_to_notifications.rb' - - 'db/migrate/20200917222316_add_index_notifications_on_type.rb' - - 'db/migrate/20201008202037_create_ip_blocks.rb' - - 'db/migrate/20201008220312_add_sign_up_ip_to_users.rb' - - 'db/migrate/20201017233919_add_suspension_origin_to_accounts.rb' - - 'db/migrate/20201206004238_create_instances.rb' - - 'db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb' - - 'db/migrate/20210221045109_create_rules.rb' - - 'db/migrate/20210306164523_account_ids_to_timestamp_ids.rb' - - 'db/migrate/20210322164601_create_account_summaries.rb' - - 'db/migrate/20210323114347_create_follow_recommendations.rb' - - 'db/migrate/20210324171613_create_follow_recommendation_suppressions.rb' - - 'db/migrate/20210416200740_create_canonical_email_blocks.rb' - - 'db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb' - - 'db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb' - - 'db/migrate/20210505174616_update_follow_recommendations_to_version_2.rb' - - 'db/migrate/20210609202149_create_login_activities.rb' - - 'db/migrate/20210616214526_create_user_ips.rb' - - 'db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb' - - 'db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb' - - 'db/migrate/20210722120340_create_account_statuses_cleanup_policies.rb' - - 'db/migrate/20210904215403_add_edited_at_to_statuses.rb' - - 'db/migrate/20210908220918_create_status_edits.rb' - - 'db/migrate/20211031031021_create_preview_card_providers.rb' - - 'db/migrate/20211112011713_add_language_to_preview_cards.rb' - - 'db/migrate/20211115032527_add_trendable_to_preview_cards.rb' - - 'db/migrate/20211123212714_add_link_type_to_preview_cards.rb' - - 'db/migrate/20211213040746_update_account_summaries_to_version_2.rb' - - 'db/migrate/20211231080958_add_category_to_reports.rb' - - 'db/migrate/20220105163928_remove_mentions_status_id_index.rb' - - 'db/migrate/20220115125126_add_report_id_to_account_warnings.rb' - - 'db/migrate/20220115125341_fix_account_warning_actions.rb' - - 'db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb' - - 'db/migrate/20220124141035_create_appeals.rb' - - 'db/migrate/20220202200743_add_trendable_to_accounts.rb' - - 'db/migrate/20220202200926_add_trendable_to_statuses.rb' - - 'db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb' - - 'db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb' - - 'db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb' - - 'db/migrate/20220302232632_add_ordered_media_attachment_ids_to_statuses.rb' - - 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb' - - 'db/migrate/20220304195405_migrate_hide_network_preference.rb' - - 'db/migrate/20220307094650_fix_featured_tags_constraints.rb' - - 'db/migrate/20220309213005_fix_reblog_deleted_at.rb' - - 'db/migrate/20220316233212_update_kurdish_locales.rb' - - 'db/migrate/20220428112511_add_index_statuses_on_account_id.rb' - - 'db/migrate/20220428112727_add_index_statuses_pins_on_status_id.rb' - - 'db/migrate/20220428114454_add_index_reports_on_assigned_account_id.rb' - - 'db/migrate/20220428114902_add_index_reports_on_action_taken_by_account_id.rb' - - 'db/migrate/20220606044941_create_webhooks.rb' - - 'db/migrate/20220611210335_create_user_roles.rb' - - 'db/migrate/20220611212541_add_role_id_to_users.rb' - - 'db/migrate/20220710102457_add_display_name_to_tags.rb' - - 'db/migrate/20220714171049_create_tag_follows.rb' - - 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb' - - 'db/migrate/20220824233535_create_status_trends.rb' - - 'db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb' - - 'db/migrate/20220829192633_add_languages_to_follows.rb' - - 'db/migrate/20220829192658_add_languages_to_follow_requests.rb' - - 'db/migrate/20221006061337_create_preview_card_trends.rb' - - 'db/migrate/20221012181003_add_blurhash_to_site_uploads.rb' - - 'db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb' - - 'db/migrate/20221025171544_add_index_ip_blocks_on_ip.rb' - - 'db/migrate/20221104133904_add_name_to_featured_tags.rb' - - 'db/post_migrate/20190519130537_remove_boosts_widening_audience.rb' - - 'db/post_migrate/20210308133107_remove_subscription_expires_at_from_accounts.rb' - - 'db/post_migrate/20220118183123_remove_rememberable_from_users.rb' - - 'db/seeds/01_web_app.rb' - - 'db/seeds/02_instance_actor.rb' - - 'db/seeds/03_roles.rb' - - 'db/seeds/04_admin.rb' - - 'lib/rails/engine_extensions.rb' - - 'lib/tasks/branding.rake' - - 'spec/fabricators_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: Exclude: @@ -1277,7 +713,6 @@ Style/MutableConstant: Exclude: - 'app/models/tag.rb' - 'app/services/delete_account_service.rb' - - 'config/initializers/twitter_regex.rb' - 'lib/mastodon/migration_warning.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1319,8 +754,6 @@ Style/RedundantConstantBase: Exclude: - 'config/environments/production.rb' - 'config/initializers/sidekiq.rb' - - 'config/locales/sr-Latn.rb' - - 'config/locales/sr.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SafeForConstants. @@ -1340,13 +773,6 @@ Style/SafeNavigation: - 'app/models/concerns/account_finder_concern.rb' - 'app/models/status.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Exclude: - - 'spec/services/activitypub/process_status_update_service_spec.rb' - - 'spec/validators/blacklisted_email_validator_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: only_raise, only_fail, semantic @@ -1360,21 +786,6 @@ Style/SingleArgumentDig: Exclude: - 'lib/webpacker/manifest_extensions.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/SlicingWithRange: - Exclude: - - 'app/lib/emoji_formatter.rb' - - 'app/lib/text_formatter.rb' - - 'app/models/account_alias.rb' - - 'app/models/domain_block.rb' - - 'app/models/email_domain_block.rb' - - 'app/models/preview_card_provider.rb' - - 'app/validators/status_length_validator.rb' - - 'db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb' - - 'lib/active_record/batches.rb' - - 'lib/mastodon/premailer_webpack_strategy.rb' - - 'lib/tasks/repo.rake' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses @@ -1444,9 +855,3 @@ Style/WordArray: - 'config/initializers/cors.rb' - 'spec/controllers/settings/imports_controller_spec.rb' - 'spec/models/form/import_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. -# URISchemes: http, https -Layout/LineLength: - Max: 701 diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f1b7bcb3..9fe1ac3803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to this project will be documented in this file. +## [4.1.6] - 2023-07-31 + +### Fixed + +- Fix memory leak in streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26228)) +- Fix wrong filters sometimes applying in streaming ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26159), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26213), [renchap](https://github.com/mastodon/mastodon/pull/26233)) +- Fix incorrect connect timeout in outgoing requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26116)) + +## [4.1.5] - 2023-07-21 + +### Added + +- Add check preventing Sidekiq workers from running with Makara configured ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25850)) + +### Changed + +- Change request timeout handling to use a longer deadline ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26055)) + +### Fixed + +- Fix moderation interface for remote instances with a .zip TLD ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25885)) +- Fix remote accounts being possibly persisted to database with incomplete protocol values ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886)) +- Fix trending publishers table not rendering correctly on narrow screens ([vmstan](https://github.com/mastodon/mastodon/pull/25945)) + +### Security + +- Fix CSP headers being unintentionally wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26105)) + ## [4.1.4] - 2023-07-07 ### Fixed @@ -143,7 +171,7 @@ All notable changes to this project will be documented in this file. - Add instance activity API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22833)) - Add setting for status page URL ([Gargron](https://github.com/mastodon/mastodon/pull/23390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23499)) - REST API changes: - - Add `configuration.urls.status` attribute to the object returned by `GET /api/v1/instance` + - Add `configuration.urls.status` attribute to the object returned by `GET /api/v2/instance` - Add `account.approved` webhook ([Saiv46](https://github.com/mastodon/mastodon/pull/22938)) - Add 12 hours option to polls ([Pleclown](https://github.com/mastodon/mastodon/pull/21131)) - Add dropdown menu item to open admin interface for remote domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21895)) diff --git a/Gemfile b/Gemfile index e41ddfdac7..41a6646e0d 100644 --- a/Gemfile +++ b/Gemfile @@ -4,14 +4,13 @@ source 'https://rubygems.org' ruby '>= 3.0.0' gem 'puma', '~> 6.3' -gem 'rails', '~> 6.1.7' +gem 'rails', '~> 7.0' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' gem 'rack', '~> 2.2.7' gem 'haml-rails', '~>2.0' gem 'pg', '~> 1.5' -gem 'makara', '~> 0.5' gem 'pghero' gem 'dotenv-rails', '~> 2.8' @@ -19,6 +18,7 @@ gem 'aws-sdk-s3', '~> 1.123', require: false gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 0.3', require: false gem 'kt-paperclip', '~> 7.2' +gem 'md-paperclip-azure', '~> 2.2', require: false gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' @@ -67,7 +67,7 @@ gem 'pundit', '~> 2.3' gem 'premailer-rails' gem 'rack-attack', '~> 6.6' gem 'rack-cors', '~> 2.0', require: 'rack/cors' -gem 'rails-i18n', '~> 6.0' +gem 'rails-i18n', '~> 7.0' gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true' gem 'redcarpet', '~> 3.6' gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] @@ -99,9 +99,6 @@ gem 'rdf-normalize', '~> 0.5' gem 'private_address_check', '~> 0.5' group :test do - # RSpec runner for rails - gem 'rspec-rails', '~> 6.0' - # Used to split testing into chunks in CI gem 'rspec_chunked', '~> 0.6' @@ -113,6 +110,10 @@ group :test do # Browser integration testing gem 'capybara', '~> 3.39' + gem 'selenium-webdriver' + + # Used to reset the database between system tests + gem 'database_cleaner-active_record' # Used to mock environment variables gem 'climate_control', '~> 0.2' @@ -159,7 +160,7 @@ group :development do gem 'letter_opener_web', '~> 2.0' # Security analysis CLI tools - gem 'brakeman', '~> 5.4', require: false + gem 'brakeman', '~> 6.0', require: false gem 'bundler-audit', '~> 0.9', require: false # Linter CLI for HAML files @@ -173,10 +174,17 @@ group :development do # Validate missing i18n keys gem 'i18n-tasks', '~> 1.0', require: false +end +group :development, :test do # Profiling tools gem 'memory_profiler', require: false + gem 'ruby-prof', require: false gem 'stackprof', require: false + gem 'test-prof' + + # RSpec runner for rails + gem 'rspec-rails', '~> 6.0' # foreman gem 'foreman' diff --git a/Gemfile.lock b/Gemfile.lock index 678e45fbcf..1766220aad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,40 +18,47 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + actioncable (7.0.6) + actionpack (= 7.0.6) + activesupport (= 7.0.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionmailbox (7.0.6) + actionpack (= 7.0.6) + activejob (= 7.0.6) + activerecord (= 7.0.6) + activestorage (= 7.0.6) + activesupport (= 7.0.6) mail (>= 2.7.1) - actionmailer (6.1.7.4) - actionpack (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activesupport (= 6.1.7.4) + net-imap + net-pop + net-smtp + actionmailer (7.0.6) + actionpack (= 7.0.6) + actionview (= 7.0.6) + activejob (= 7.0.6) + activesupport (= 7.0.6) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.4) - actionview (= 6.1.7.4) - activesupport (= 6.1.7.4) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.6) + actionview (= 7.0.6) + activesupport (= 7.0.6) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.4) - actionpack (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actiontext (7.0.6) + actionpack (= 7.0.6) + activerecord (= 7.0.6) + activestorage (= 7.0.6) + activesupport (= 7.0.6) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.4) - activesupport (= 6.1.7.4) + actionview (7.0.6) + activesupport (= 7.0.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -61,27 +68,26 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.4) - activesupport (= 6.1.7.4) + activejob (7.0.6) + activesupport (= 7.0.6) globalid (>= 0.3.6) - activemodel (6.1.7.4) - activesupport (= 6.1.7.4) - activerecord (6.1.7.4) - activemodel (= 6.1.7.4) - activesupport (= 6.1.7.4) - activestorage (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activesupport (= 6.1.7.4) + activemodel (7.0.6) + activesupport (= 7.0.6) + activerecord (7.0.6) + activemodel (= 7.0.6) + activesupport (= 7.0.6) + activestorage (7.0.6) + actionpack (= 7.0.6) + activejob (= 7.0.6) + activerecord (= 7.0.6) + activesupport (= 7.0.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.4) + activesupport (7.0.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) @@ -97,21 +103,29 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.780.0) - aws-sdk-core (3.175.0) + aws-partitions (1.791.0) + aws-sdk-core (3.178.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.67.0) - aws-sdk-core (~> 3, >= 3.174.0) + aws-sdk-kms (1.71.0) + aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.126.0) - aws-sdk-core (~> 3, >= 3.174.0) + aws-sdk-s3 (1.131.0) + aws-sdk-core (~> 3, >= 3.177.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.0) aws-eventstream (~> 1, >= 1.0.2) + azure-storage-blob (2.0.3) + azure-storage-common (~> 2.0) + nokogiri (~> 1, >= 1.10.8) + azure-storage-common (2.0.4) + faraday (~> 1.0) + faraday_middleware (~> 1.0, >= 1.0.0.rc1) + net-http-persistent (~> 4.0) + nokogiri (~> 1, >= 1.10.8) bcrypt (3.1.18) better_errors (2.10.1) erubi (>= 1.0.0) @@ -130,7 +144,7 @@ GEM blurhash (0.1.7) bootsnap (1.16.0) msgpack (~> 1.2) - brakeman (5.4.1) + brakeman (6.0.1) browser (5.3.1) brpoplpush-redis_script (0.1.3) concurrent-ruby (~> 1.0, >= 1.0.5) @@ -146,7 +160,7 @@ GEM sshkit (>= 1.9.0) capistrano-bundler (2.1.0) capistrano (~> 3.1) - capistrano-rails (1.6.2) + capistrano-rails (1.6.3) capistrano (~> 3.1) capistrano-bundler (>= 1.1, < 3) capistrano-rbenv (2.2.0) @@ -167,7 +181,7 @@ GEM activesupport cbor (0.5.9.6) charlock_holmes (0.7.7) - chewy (7.3.2) + chewy (7.3.3) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl @@ -185,6 +199,10 @@ GEM crass (1.0.6) css_parser (1.14.0) addressable + database_cleaner-active_record (2.1.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) date (3.3.3) debug_inspector (1.1.0) devise (4.9.2) @@ -255,6 +273,8 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) fast_blank (1.0.1) fastimage (2.2.7) ffi (1.15.5) @@ -292,7 +312,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.46.0) + haml_lint (0.49.2) haml (>= 4.0, < 6.2) parallel (~> 1.10) rainbow @@ -401,12 +421,14 @@ GEM net-imap net-pop net-smtp - makara (0.5.1) - activerecord (>= 5.2.0) marcel (1.0.2) mario-redis-lock (1.2.1) redis (>= 3.0.5) matrix (0.4.2) + md-paperclip-azure (2.2.0) + addressable (~> 2.5) + azure-storage-blob (~> 2.0.1) + hashie (~> 5.0) memory_profiler (1.0.1) method_source (1.0.0) mime-types (3.4.1) @@ -420,6 +442,8 @@ GEM multipart-post (2.3.0) net-http (0.3.2) uri + net-http-persistent (4.0.2) + connection_pool (~> 2.2) net-imap (0.3.6) date net-protocol @@ -469,7 +493,7 @@ GEM openssl-signature_algorithm (1.3.0) openssl (> 2.0) orm_adapter (0.5.0) - ox (2.14.16) + ox (2.14.17) parallel (1.23.0) parser (3.2.2.3) ast (~> 2.4.1) @@ -490,7 +514,7 @@ GEM net-smtp premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) - public_suffix (5.0.1) + public_suffix (5.0.3) puma (6.3.0) nio4r (~> 2.0) pundit (2.3.0) @@ -512,21 +536,20 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.4) - actioncable (= 6.1.7.4) - actionmailbox (= 6.1.7.4) - actionmailer (= 6.1.7.4) - actionpack (= 6.1.7.4) - actiontext (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activemodel (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + rails (7.0.6) + actioncable (= 7.0.6) + actionmailbox (= 7.0.6) + actionmailer (= 7.0.6) + actionpack (= 7.0.6) + actiontext (= 7.0.6) + actionview (= 7.0.6) + activejob (= 7.0.6) + activemodel (= 7.0.6) + activerecord (= 7.0.6) + activestorage (= 7.0.6) + activesupport (= 7.0.6) bundler (>= 1.15.0) - railties (= 6.1.7.4) - sprockets-rails (>= 2.0.0) + railties (= 7.0.6) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -537,20 +560,21 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - rails-i18n (6.0.0) + rails-i18n (7.0.7) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 7) - railties (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + railties (>= 6.0.0, < 8) + railties (7.0.6) + actionpack (= 7.0.6) + activesupport (= 7.0.6) method_source rake (>= 12.2) thor (~> 1.0) + zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) rdf (3.2.11) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.6.0) + rdf-normalize (0.6.1) rdf (~> 3.2) redcarpet (3.6.0) redis (4.8.1) @@ -564,7 +588,7 @@ GEM responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.5) + rexml (3.2.6) rotp (6.2.2) rouge (4.1.2) rpam2 (4.0.2) @@ -593,7 +617,7 @@ GEM sidekiq (>= 2.4.0) rspec-support (3.12.0) rspec_chunked (0.6) - rubocop (1.54.1) + rubocop (1.54.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -613,7 +637,7 @@ GEM rubocop-performance (1.18.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.19.1) + rubocop-rails (2.20.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) @@ -621,6 +645,7 @@ GEM rubocop (~> 1.33) rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) + ruby-prof (1.6.3) ruby-progressbar (1.13.0) ruby-saml (1.15.0) nokogiri (>= 1.13.10) @@ -637,6 +662,10 @@ GEM scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) + selenium-webdriver (4.9.1) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) semantic_range (3.0.0) sidekiq (6.5.9) connection_pool (>= 2.2.5, < 3) @@ -673,7 +702,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.21.4) + sshkit (1.21.5) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.25) @@ -691,9 +720,10 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) + test-prof (1.2.1) thor (1.2.2) tilt (2.2.0) - timeout (0.3.2) + timeout (0.4.0) tpm-key_attestation (0.12.0) bindata (~> 2.4) openssl (> 2.0) @@ -749,6 +779,7 @@ GEM rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) + websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -770,7 +801,7 @@ DEPENDENCIES binding_of_caller (~> 1.0) blurhash (~> 0.1) bootsnap (~> 1.16.0) - brakeman (~> 5.4) + brakeman (~> 6.0) browser bundler-audit (~> 0.9) capistrano (~> 3.17) @@ -785,6 +816,7 @@ DEPENDENCIES color_diff (~> 0.1) concurrent-ruby connection_pool + database_cleaner-active_record devise (~> 4.9) devise-two-factor (~> 4.1) devise_pam_authenticatable2 (~> 9.2) @@ -819,8 +851,8 @@ DEPENDENCIES letter_opener_web (~> 2.0) link_header (~> 0.0) lograge (~> 0.12) - makara (~> 0.5) mario-redis-lock (~> 1.2) + md-paperclip-azure (~> 2.2) memory_profiler mime-types (~> 3.4.1) net-http (~> 0.3.2) @@ -846,9 +878,9 @@ DEPENDENCIES rack-attack (~> 6.6) rack-cors (~> 2.0) rack-test (~> 2.1) - rails (~> 6.1.7) + rails (~> 7.0) rails-controller-testing (~> 1.0) - rails-i18n (~> 6.0) + rails-i18n (~> 7.0) rails-settings-cached (~> 0.6)! rdf-normalize (~> 0.5) redcarpet (~> 3.6) @@ -863,10 +895,12 @@ DEPENDENCIES rubocop-performance rubocop-rails rubocop-rspec + ruby-prof ruby-progressbar (~> 1.13) rubyzip (~> 2.3) sanitize (~> 6.0) scenic (~> 1.7) + selenium-webdriver sidekiq (~> 6.5) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 5.0) @@ -879,6 +913,7 @@ DEPENDENCIES stackprof stoplight (~> 3.0.1) strong_migrations (~> 0.8) + test-prof thor (~> 1.2) tty-prompt (~> 0.23) twitter-text (~> 3.1.0) diff --git a/Procfile.dev b/Procfile.dev index a7802cef90..7dd6f2e1ac 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq stream: env PORT=4000 yarn run start -webpack: env RAILS_ENV=development NODE_ENV=development ./bin/webpack-dev-server --listen-host 0.0.0.0 +webpack: env RAILS_ENV=development NODE_ENV=development NODE_OPTIONS=--openssl-legacy-provider ./bin/webpack-dev-server --listen-host 0.0.0.0 diff --git a/Rakefile b/Rakefile index b8ebefccd0..5ad80bcfcc 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) Rails.application.load_tasks diff --git a/app/chewy/instances_index.rb b/app/chewy/instances_index.rb new file mode 100644 index 0000000000..2bce4043c9 --- /dev/null +++ b/app/chewy/instances_index.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class InstancesIndex < Chewy::Index + settings index: { refresh_interval: '30s' } + + index_scope ::Instance.searchable + + root date_detection: false do + field :domain, type: 'text', index_prefixes: { min_chars: 1 } + field :accounts_count, type: 'long' + end +end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 929bb54aa7..32fc378790 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -12,7 +12,7 @@ class AccountsController < ApplicationController before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) } - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? def show respond_to do |format| diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 5194057263..e5a55de06d 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -65,7 +65,7 @@ module Admin end def filtered_instances - InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results + InstanceFilter.new(limited_federation_mode? ? { allowed: true } : filter_params).results end def filter_params diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 2629ab782f..c764b45101 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -8,7 +8,7 @@ class Api::BaseController < ApplicationController include AccessTokenTrackingConcern include ApiCachingConcern - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? before_action :require_not_suspended! @@ -150,7 +150,7 @@ class Api::BaseController < ApplicationController end def disallow_unauthenticated_api_access? - ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode + ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.limited_federation_mode end private diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index 0cc2318409..498eb16f44 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -21,7 +21,7 @@ class Api::V1::BookmarksController < Api::BaseController end def results - @_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id( + @results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id( limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id) ) diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index bd7f3d775e..faf1bda96a 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -21,7 +21,7 @@ class Api::V1::FavouritesController < Api::BaseController end def results - @_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id( + @results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id( limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id) ) diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index 3d55d990af..9da77f8dab 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -3,7 +3,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController before_action :require_enabled_api! - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? vary_by '' @@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController end def require_enabled_api! - head 404 unless Setting.activity_api_enabled && !whitelist_mode? + head 404 unless Setting.activity_api_enabled && !limited_federation_mode? end end diff --git a/app/controllers/api/v1/instances/domain_blocks_controller.rb b/app/controllers/api/v1/instances/domain_blocks_controller.rb index e954c45897..c91234e088 100644 --- a/app/controllers/api/v1/instances/domain_blocks_controller.rb +++ b/app/controllers/api/v1/instances/domain_blocks_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Instances::DomainBlocksController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? before_action :require_enabled_api! before_action :set_domain_blocks diff --git a/app/controllers/api/v1/instances/extended_descriptions_controller.rb b/app/controllers/api/v1/instances/extended_descriptions_controller.rb index a0665725bd..376fec9066 100644 --- a/app/controllers/api/v1/instances/extended_descriptions_controller.rb +++ b/app/controllers/api/v1/instances/extended_descriptions_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale before_action :set_extended_description @@ -10,7 +10,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController # Override `current_user` to avoid reading session cookies unless in whitelist mode def current_user - super if whitelist_mode? + super if limited_federation_mode? end def show diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index 70281362a8..08a982f227 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -3,24 +3,24 @@ class Api::V1::Instances::PeersController < Api::BaseController before_action :require_enabled_api! - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale vary_by '' # Override `current_user` to avoid reading session cookies unless in whitelist mode def current_user - super if whitelist_mode? + super if limited_federation_mode? end def index cache_even_if_authenticated! - render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) } + render_with_cache(expires_in: 1.day) { Instance.searchable.pluck(:domain) } end private def require_enabled_api! - head 404 unless Setting.peers_api_enabled && !whitelist_mode? + head 404 unless Setting.peers_api_enabled && !limited_federation_mode? end end diff --git a/app/controllers/api/v1/instances/privacy_policies_controller.rb b/app/controllers/api/v1/instances/privacy_policies_controller.rb index 36889f7335..f5b1b4ec5f 100644 --- a/app/controllers/api/v1/instances/privacy_policies_controller.rb +++ b/app/controllers/api/v1/instances/privacy_policies_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? before_action :set_privacy_policy diff --git a/app/controllers/api/v1/instances/rules_controller.rb b/app/controllers/api/v1/instances/rules_controller.rb index d3eeca3262..2f71984b05 100644 --- a/app/controllers/api/v1/instances/rules_controller.rb +++ b/app/controllers/api/v1/instances/rules_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Instances::RulesController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale before_action :set_rules @@ -10,7 +10,7 @@ class Api::V1::Instances::RulesController < Api::BaseController # Override `current_user` to avoid reading session cookies unless in whitelist mode def current_user - super if whitelist_mode? + super if limited_federation_mode? end def index diff --git a/app/controllers/api/v1/instances/translation_languages_controller.rb b/app/controllers/api/v1/instances/translation_languages_controller.rb index c4680cccb8..78423e40e4 100644 --- a/app/controllers/api/v1/instances/translation_languages_controller.rb +++ b/app/controllers/api/v1/instances/translation_languages_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Instances::TranslationLanguagesController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? before_action :set_languages diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 5a6701ff96..df4a14af15 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true class Api::V1::InstancesController < Api::BaseController - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale vary_by '' # Override `current_user` to avoid reading session cookies unless in whitelist mode def current_user - super if whitelist_mode? + super if limited_federation_mode? end def show diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb index 867e6facf4..f8dfba8a94 100644 --- a/app/controllers/api/v1/markers_controller.rb +++ b/app/controllers/api/v1/markers_controller.rb @@ -7,7 +7,10 @@ class Api::V1::MarkersController < Api::BaseController before_action :require_user! def index - @markers = current_user.markers.where(timeline: Array(params[:timeline])).index_by(&:timeline) + with_read_replica do + @markers = current_user.markers.where(timeline: Array(params[:timeline])).index_by(&:timeline) + end + render json: serialize_map(@markers) end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 7a64d13005..b1814e16ab 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -9,8 +9,12 @@ class Api::V1::NotificationsController < Api::BaseController DEFAULT_NOTIFICATIONS_LIMIT = 40 def index - @notifications = load_notifications - render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) + with_read_replica do + @notifications = load_notifications + @relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) + end + + render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: @relationships end def show diff --git a/app/controllers/api/v1/peers/search_controller.rb b/app/controllers/api/v1/peers/search_controller.rb new file mode 100644 index 0000000000..2c0eacdcae --- /dev/null +++ b/app/controllers/api/v1/peers/search_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Api::V1::Peers::SearchController < Api::BaseController + before_action :require_enabled_api! + before_action :set_domains + + skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? + skip_around_action :set_locale + + vary_by '' + + def index + cache_even_if_authenticated! + render json: @domains + end + + private + + def require_enabled_api! + head 404 unless Setting.peers_api_enabled && !limited_federation_mode? + end + + def set_domains + return if params[:q].blank? + + if Chewy.enabled? + @domains = InstancesIndex.query(function_score: { + query: { + prefix: { + domain: TagManager.instance.normalize_domain(params[:q].strip), + }, + }, + + field_value_factor: { + field: 'accounts_count', + modifier: 'log2p', + }, + }).limit(10).pluck(:domain) + else + domain = params[:q].strip + domain = TagManager.instance.normalize_domain(domain) + @domains = Instance.searchable.where(Instance.arel_table[:domain].matches("#{Instance.sanitize_sql_like(domain)}%", false, true)).limit(10).pluck(:domain) + end + end +end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 8ff6c8fe5c..300c9faa3f 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -23,6 +23,6 @@ class Api::V1::ReportsController < Api::BaseController end def report_params - params.permit(:account_id, :comment, :category, :forward, status_ids: [], rule_ids: []) + params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: []) end end diff --git a/app/controllers/api/v1/statuses/favourites_controller.rb b/app/controllers/api/v1/statuses/favourites_controller.rb index 2e21ce6a06..f3428e3df4 100644 --- a/app/controllers/api/v1/statuses/favourites_controller.rb +++ b/app/controllers/api/v1/statuses/favourites_controller.rb @@ -17,13 +17,16 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController if fav @status = fav.status + count = [@status.favourites_count - 1, 0].max UnfavouriteWorker.perform_async(current_account.id, @status.id) else @status = Status.find(params[:status_id]) + count = @status.favourites_count authorize @status, :show? end - render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }) + relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } }) + render json: @status, serializer: REST::StatusSerializer, relationships: relationships rescue Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index e3769437b7..3ca6231178 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -24,15 +24,18 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController if @status authorize @status, :unreblog? + @reblog = @status.reblog + count = [@reblog.reblogs_count - 1, 0].max @status.discard RemovalWorker.perform_async(@status.id) - @reblog = @status.reblog else @reblog = Status.find(params[:status_id]) + count = @reblog.reblogs_count authorize @reblog, :show? end - render json: @reblog, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }) + relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } }) + render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships rescue Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/tags_controller.rb b/app/controllers/api/v1/tags_controller.rb index 284ec85937..672535a018 100644 --- a/app/controllers/api/v1/tags_controller.rb +++ b/app/controllers/api/v1/tags_controller.rb @@ -19,6 +19,7 @@ class Api::V1::TagsController < Api::BaseController def unfollow TagFollow.find_by(account: current_account, tag: @tag)&.destroy! + TagUnmergeWorker.perform_async(@tag.id, current_account.id) render json: @tag, serializer: REST::TagSerializer end diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index ae6dbcb8b3..83b8cb4c66 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -6,11 +6,14 @@ class Api::V1::Timelines::HomeController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show - @statuses = load_statuses + with_read_replica do + @statuses = load_statuses + @relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + end render json: @statuses, each_serializer: REST::StatusSerializer, - relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + relationships: @relationships, status: account_home_feed.regenerating? ? 206 : 200 end diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index 58f6345e68..63c3f2d90a 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -1,25 +1,36 @@ # frozen_string_literal: true class Api::Web::EmbedsController < Api::Web::BaseController - before_action :require_user! + include Authorization - def create - status = StatusFinder.new(params[:url]).status + before_action :set_status - return not_found if status.hidden? + def show + return not_found if @status.hidden? - render json: status, serializer: OEmbedSerializer, width: 400 - rescue ActiveRecord::RecordNotFound - oembed = FetchOEmbedService.new.call(params[:url]) + if @status.local? + render json: @status, serializer: OEmbedSerializer, width: 400 + else + return not_found unless user_signed_in? - return not_found if oembed.nil? + url = ActivityPub::TagManager.instance.url_for(@status) + oembed = FetchOEmbedService.new.call(url) + return not_found if oembed.nil? - begin - oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED) - rescue ArgumentError - return not_found + begin + oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED) + rescue ArgumentError + return not_found + end + + render json: oembed end + end - render json: oembed + def set_status + @status = Status.find(params[:id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7c09040fbf..c57031da3c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base include CacheConcern include DomainControlHelper include ThemingConcern + include DatabaseHelper helper_method :current_account helper_method :current_session @@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base helper_method :use_seamless_external_login? helper_method :omniauth_only? helper_method :sso_account_settings - helper_method :whitelist_mode? + helper_method :limited_federation_mode? helper_method :body_class_string helper_method :skip_csrf_meta_tags? @@ -53,7 +54,7 @@ class ApplicationController < ActionController::Base private def authorized_fetch_mode? - ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode + ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.limited_federation_mode end def public_fetch_mode? diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb index 9e0fb942aa..4723806b92 100644 --- a/app/controllers/auth/omniauth_callbacks_controller.rb +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -5,21 +5,13 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController def self.provides_callback_for(provider) define_method provider do + @provider = provider @user = User.find_for_oauth(request.env['omniauth.auth'], current_user) if @user.persisted? - LoginActivity.create( - user: @user, - success: true, - authentication_method: :omniauth, - provider: provider, - ip: request.remote_ip, - user_agent: request.user_agent - ) - + record_login_activity sign_in_and_redirect @user, event: :authentication - label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize) - set_flash_message(:notice, :success, kind: label) if is_navigational_format? + set_flash_message(:notice, :success, kind: label_for_provider) if is_navigational_format? else session["devise.#{provider}_data"] = request.env['omniauth.auth'] redirect_to new_user_registration_url @@ -38,4 +30,29 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController auth_setup_path(missing_email: '1') end end + + private + + def record_login_activity + LoginActivity.create( + user: @user, + success: true, + authentication_method: :omniauth, + provider: @provider, + ip: request.remote_ip, + user_agent: request.user_agent + ) + end + + def label_for_provider + provider_display_name || configured_provider_name + end + + def provider_display_name + Devise.omniauth_configs[@provider]&.strategy&.display_name.presence + end + + def configured_provider_name + I18n.t("auth.providers.#{@provider}", default: @provider.to_s.chomp('_oauth2').capitalize) + end end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index b1abb9f1df..d59250b31c 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -113,7 +113,7 @@ class Auth::SessionsController < Devise::SessionsController end def home_paths(resource) - paths = [about_path] + paths = [about_path, '/explore'] paths << short_account_path(username: resource.account) if single_user_mode? && resource.is_a?(User) @@ -129,7 +129,7 @@ class Auth::SessionsController < Devise::SessionsController redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout') end - def set_attempt_session(user) + def register_attempt_in_session(user) session[:attempt_user_id] = user.id session[:attempt_user_updated_at] = user.updated_at.to_s end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index 73f0f2b88d..99eed018b0 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -3,33 +3,19 @@ class AuthorizeInteractionsController < ApplicationController include Authorization - layout 'modal' - before_action :authenticate_user! - before_action :set_body_classes before_action :set_resource - before_action :set_pack def show if @resource.is_a?(Account) - render :show + redirect_to web_url("@#{@resource.pretty_acct}") elsif @resource.is_a?(Status) redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}") else - render :error + not_found end end - def create - if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource, with_rate_limit: true) - render :success - else - render :error - end - rescue ActiveRecord::RecordNotFound - render :error - end - private def set_resource @@ -62,12 +48,4 @@ class AuthorizeInteractionsController < ApplicationController def uri_param params[:uri] || params.fetch(:acct, '').delete_prefix('acct:') end - - def set_body_classes - @body_classes = 'modal-layout' - end - - def set_pack - use_pack 'modal' - end end diff --git a/app/controllers/backups_controller.rb b/app/controllers/backups_controller.rb index 205df48d44..db23fefbbc 100644 --- a/app/controllers/backups_controller.rb +++ b/app/controllers/backups_controller.rb @@ -10,7 +10,7 @@ class BackupsController < ApplicationController def download case Paperclip::Attachment.default_options[:storage] - when :s3 + when :s3, :azure redirect_to @backup.dump.expiring_url(10), allow_other_host: true when :fog if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present? diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb index 25149d03fb..3fc0938bfc 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -4,7 +4,7 @@ module AccountOwnedConcern extend ActiveSupport::Concern included do - before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json } + before_action :authenticate_user!, if: -> { limited_federation_mode? && request.format != :json } before_action :set_account, if: :account_required? before_action :check_account_approval, if: :account_required? before_action :check_account_suspension, if: :account_required? diff --git a/app/controllers/concerns/api_caching_concern.rb b/app/controllers/concerns/api_caching_concern.rb index 705abce80f..12264d514e 100644 --- a/app/controllers/concerns/api_caching_concern.rb +++ b/app/controllers/concerns/api_caching_concern.rb @@ -8,6 +8,6 @@ module ApiCachingConcern end def cache_even_if_authenticated! - expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless whitelist_mode? + expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless limited_federation_mode? end end diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb index 30702f00e7..5b83d8575b 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/rate_limit_headers.rb @@ -61,7 +61,7 @@ module RateLimitHeaders end def request_time - @_request_time ||= Time.now.utc + @request_time ||= Time.now.utc end def reset_period_offset diff --git a/app/controllers/concerns/two_factor_authentication_concern.rb b/app/controllers/concerns/two_factor_authentication_concern.rb index b30cd354d2..ed0175581c 100644 --- a/app/controllers/concerns/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/two_factor_authentication_concern.rb @@ -75,7 +75,7 @@ module TwoFactorAuthenticationConcern end def prompt_for_two_factor(user) - set_attempt_session(user) + register_attempt_in_session(user) use_pack 'auth' diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 2e55cf6c34..55f9fb9265 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -10,7 +10,7 @@ class FollowerAccountsController < ApplicationController before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } skip_around_action :set_locale, if: -> { request.format == :json } - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? def index respond_to do |format| diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 2aa31bdf08..cce296f9fd 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -10,7 +10,7 @@ class FollowingAccountsController < ApplicationController before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } skip_around_action :set_locale, if: -> { request.format == :json } - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? def index respond_to do |format| diff --git a/app/controllers/mail_subscriptions_controller.rb b/app/controllers/mail_subscriptions_controller.rb index b071a80605..1caeaaacf4 100644 --- a/app/controllers/mail_subscriptions_controller.rb +++ b/app/controllers/mail_subscriptions_controller.rb @@ -9,6 +9,8 @@ class MailSubscriptionsController < ApplicationController before_action :set_user before_action :set_type + protect_from_forgery with: :null_session + def show; end def create @@ -20,6 +22,7 @@ class MailSubscriptionsController < ApplicationController def set_user @user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe') + not_found unless @user end def set_body_classes @@ -35,7 +38,7 @@ class MailSubscriptionsController < ApplicationController when 'follow', 'reblog', 'favourite', 'mention', 'follow_request' "notification_emails.#{params[:type]}" else - raise ArgumentError + not_found end end end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index ac820e92bc..4c028dbef0 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -3,9 +3,9 @@ class MediaController < ApplicationController include Authorization - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? - before_action :authenticate_user!, if: :whitelist_mode? + before_action :authenticate_user!, if: :limited_federation_mode? before_action :set_media_attachment before_action :verify_permitted_status! before_action :check_playable, only: :player diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb index 8d480d704e..c4230d62c3 100644 --- a/app/controllers/media_proxy_controller.rb +++ b/app/controllers/media_proxy_controller.rb @@ -8,7 +8,7 @@ class MediaProxyController < ApplicationController skip_before_action :require_functional! - before_action :authenticate_user!, if: :whitelist_mode? + before_action :authenticate_user!, if: :limited_federation_mode? rescue_from ActiveRecord::RecordInvalid, with: :not_found rescue_from Mastodon::UnexpectedResponseError, with: :not_found diff --git a/app/controllers/remote_interaction_helper_controller.rb b/app/controllers/remote_interaction_helper_controller.rb new file mode 100644 index 0000000000..90c853f47b --- /dev/null +++ b/app/controllers/remote_interaction_helper_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class RemoteInteractionHelperController < ApplicationController + vary_by '' + + skip_before_action :require_functional! + skip_around_action :set_locale + skip_before_action :update_user_sign_in + + content_security_policy do |p| + # We inherit the normal `script-src` + + # Set every directive that does not have a fallback + p.default_src :none + p.form_action :none + p.base_uri :none + + # Disable every directive with a fallback to cut on response size + p.base_uri false + p.font_src false + p.img_src false + p.style_src false + p.media_src false + p.frame_src false + p.manifest_src false + p.connect_src false + p.child_src false + p.worker_src false + + # Widen the directives that we do need + p.frame_ancestors :self + p.connect_src :https + end + + def index + expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) + + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + response.headers['Referrer-Policy'] = 'no-referrer' + + render layout: 'helper_frame' + end +end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 0efafb8456..826a013cdc 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -17,7 +17,7 @@ class StatusesController < ApplicationController after_action :set_link_headers skip_around_action :set_locale, if: -> { request.format == :json } - skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode? + skip_before_action :require_functional!, only: [:show, :embed], unless: :limited_federation_mode? content_security_policy only: :embed do |policy| policy.frame_ancestors(false) diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 7e249dbea5..2007fe8462 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -10,13 +10,13 @@ class TagsController < ApplicationController vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } - before_action :authenticate_user!, if: :whitelist_mode? + before_action :authenticate_user!, if: :limited_federation_mode? before_action :set_local before_action :set_tag before_action :set_statuses, if: -> { request.format == :rss } before_action :set_instance_presenter - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional!, unless: :limited_federation_mode? def show respond_to do |format| diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 0d897e8e24..4748940f7c 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -19,6 +19,7 @@ module WellKnown def set_account username = username_from_resource + @account = begin if username == Rails.configuration.x.local_domain Account.representative diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3148756b75..b85c8fe843 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -236,6 +236,6 @@ module ApplicationHelper private def storage_host_var - ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) + ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) || ENV.fetch('AZURE_ALIAS_HOST', nil) end end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 69d3be7527..87057a8b58 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -22,7 +22,14 @@ module ContextHelper blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' }, discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' }, voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, - olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' }, + olm: { + 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', + 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, + 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, + 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, + 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, + 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' + }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' }, }.freeze diff --git a/app/helpers/database_helper.rb b/app/helpers/database_helper.rb new file mode 100644 index 0000000000..79227bb109 --- /dev/null +++ b/app/helpers/database_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module DatabaseHelper + def with_read_replica(&block) + ApplicationRecord.connected_to(role: :reading, prevent_writes: true, &block) + end + + def with_primary(&block) + ApplicationRecord.connected_to(role: :writing, &block) + end +end diff --git a/app/helpers/domain_control_helper.rb b/app/helpers/domain_control_helper.rb index ffcf375ea7..2703c1b8f8 100644 --- a/app/helpers/domain_control_helper.rb +++ b/app/helpers/domain_control_helper.rb @@ -2,7 +2,7 @@ module DomainControlHelper def domain_not_allowed?(uri_or_domain) - return if uri_or_domain.blank? + return false if uri_or_domain.blank? domain = if uri_or_domain.include?('://') Addressable::URI.parse(uri_or_domain).host @@ -10,14 +10,14 @@ module DomainControlHelper uri_or_domain end - if whitelist_mode? + if limited_federation_mode? !DomainAllow.allowed?(domain) else DomainBlock.blocked?(domain) end end - def whitelist_mode? - Rails.configuration.x.whitelist_mode + def limited_federation_mode? + Rails.configuration.x.limited_federation_mode end end diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index 840a18d3e6..1f400b477a 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -204,7 +204,17 @@ module LanguagesHelper zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze, }.freeze - SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_3).freeze + # e.g. For Chinese, which is not a language, + # but a language family in spite of sharing the main locale code + # We need to be able to filter these + ISO_639_1_REGIONAL = { + 'zh-CN': ['Chinese (China)', '简体中文'].freeze, + 'zh-HK': ['Chinese (Hong Kong)', '繁體中文(香港)'].freeze, + 'zh-TW': ['Chinese (Taiwan)', '繁體中文(臺灣)'].freeze, + 'zh-YUE': ['Cantonese', '廣東話'].freeze, + }.freeze + + SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_1_REGIONAL).merge(ISO_639_3).freeze # For ISO-639-1 and ISO-639-3 language codes, we have their official # names, but for some translations, we need the names of the @@ -217,9 +227,6 @@ module LanguagesHelper 'pt-BR': 'Português (Brasil)', 'pt-PT': 'Português (Portugal)', 'sr-Latn': 'Srpski (latinica)', - 'zh-CN': '简体中文', - 'zh-HK': '繁體中文(香港)', - 'zh-TW': '繁體中文(臺灣)', }.freeze def native_locale_name(locale) diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index f1f1ea872e..286c53d834 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -65,33 +65,6 @@ module StatusesHelper embedded_view? ? '_blank' : nil end - def style_classes(status, is_predecessor, is_successor, include_threads) - classes = ['entry'] - classes << 'entry-predecessor' if is_predecessor - classes << 'entry-reblog' if status.reblog? - classes << 'entry-successor' if is_successor - classes << 'entry-center' if include_threads - classes.join(' ') - end - - def microformats_classes(status, is_direct_parent, is_direct_child) - classes = [] - classes << 'p-in-reply-to' if is_direct_parent - classes << 'p-repost-of' if status.reblog? && is_direct_parent - classes << 'p-comment' if is_direct_child - classes.join(' ') - end - - def microformats_h_class(status, is_predecessor, is_successor, include_threads) - if is_predecessor || status.reblog? || is_successor - 'h-cite' - elsif include_threads - '' - else - 'h-entry' - end - end - def fa_visibility_icon(status) case status.visibility when 'public' diff --git a/app/javascript/core/remote_interaction_helper.ts b/app/javascript/core/remote_interaction_helper.ts new file mode 100644 index 0000000000..53d95b5dbe --- /dev/null +++ b/app/javascript/core/remote_interaction_helper.ts @@ -0,0 +1,172 @@ +/* + +This script is meant to to be used in an `iframe` with the sole purpose of doing webfinger queries +client-side without being restricted by a strict `connect-src` Content-Security-Policy directive. + +It communicates with the parent window through message events that are authenticated by origin, +and performs no other task. + +*/ + +import 'packs/public-path'; + +import axios from 'axios'; + +interface JRDLink { + rel: string; + template?: string; + href?: string; +} + +const isJRDLink = (link: unknown): link is JRDLink => + typeof link === 'object' && + link !== null && + 'rel' in link && + typeof link.rel === 'string' && + (!('template' in link) || typeof link.template === 'string') && + (!('href' in link) || typeof link.href === 'string'); + +const findLink = (rel: string, data: unknown): JRDLink | undefined => { + if ( + typeof data === 'object' && + data !== null && + 'links' in data && + data.links instanceof Array + ) { + return data.links.find( + (link): link is JRDLink => isJRDLink(link) && link.rel === rel, + ); + } else { + return undefined; + } +}; + +const findTemplateLink = (data: unknown) => + findLink('http://ostatus.org/schema/1.0/subscribe', data)?.template; + +const fetchInteractionURLSuccess = ( + uri_or_domain: string, + template: string, +) => { + window.parent.postMessage( + { + type: 'fetchInteractionURL-success', + uri_or_domain, + template, + }, + window.origin, + ); +}; + +const fetchInteractionURLFailure = () => { + window.parent.postMessage( + { + type: 'fetchInteractionURL-failure', + }, + window.origin, + ); +}; + +const isValidDomain = (value: string) => { + const url = new URL('https:///path'); + url.hostname = value; + return url.hostname === value; +}; + +// Attempt to find a remote interaction URL from a domain +const fromDomain = (domain: string) => { + const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; + + axios + .get(`https://${domain}/.well-known/webfinger`, { + params: { resource: `https://${domain}` }, + }) + .then(({ data }) => { + const template = findTemplateLink(data); + fetchInteractionURLSuccess(domain, template ?? fallbackTemplate); + return; + }) + .catch(() => { + fetchInteractionURLSuccess(domain, fallbackTemplate); + }); +}; + +// Attempt to find a remote interaction URL from an arbitrary URL +const fromURL = (url: string) => { + const domain = new URL(url).host; + const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; + + axios + .get(`https://${domain}/.well-known/webfinger`, { + params: { resource: url }, + }) + .then(({ data }) => { + const template = findTemplateLink(data); + fetchInteractionURLSuccess(url, template ?? fallbackTemplate); + return; + }) + .catch(() => { + fromDomain(domain); + }); +}; + +// Attempt to find a remote interaction URL from a `user@domain` string +const fromAcct = (acct: string) => { + acct = acct.replace(/^@/, ''); + + const segments = acct.split('@'); + + if (segments.length !== 2 || !segments[0] || !isValidDomain(segments[1])) { + fetchInteractionURLFailure(); + return; + } + + const domain = segments[1]; + const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; + + axios + .get(`https://${domain}/.well-known/webfinger`, { + params: { resource: `acct:${acct}` }, + }) + .then(({ data }) => { + const template = findTemplateLink(data); + fetchInteractionURLSuccess(acct, template ?? fallbackTemplate); + return; + }) + .catch(() => { + // TODO: handle host-meta? + fromDomain(domain); + }); +}; + +const fetchInteractionURL = (uri_or_domain: string) => { + if (/^https?:\/\//.test(uri_or_domain)) { + fromURL(uri_or_domain); + } else if (uri_or_domain.includes('@')) { + fromAcct(uri_or_domain); + } else { + fromDomain(uri_or_domain); + } +}; + +window.addEventListener('message', (event: MessageEvent) => { + // Check message origin + if ( + !window.origin || + window.parent !== event.source || + event.origin !== window.origin + ) { + return; + } + + if ( + event.data && + typeof event.data === 'object' && + 'type' in event.data && + event.data.type === 'fetchInteractionURL' && + 'uri_or_domain' in event.data && + typeof event.data.uri_or_domain === 'string' + ) { + fetchInteractionURL(event.data.uri_or_domain); + } +}); diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml index 30676dcf58..20912e28d0 100644 --- a/app/javascript/core/theme.yml +++ b/app/javascript/core/theme.yml @@ -18,3 +18,4 @@ pack: settings: settings.js sign_up: share: + remote_interaction_helper: remote_interaction_helper.ts diff --git a/app/javascript/flavours/glitch/actions/importer/index.js b/app/javascript/flavours/glitch/actions/importer/index.js index 94d133b5f3..3d01a96dd8 100644 --- a/app/javascript/flavours/glitch/actions/importer/index.js +++ b/app/javascript/flavours/glitch/actions/importer/index.js @@ -81,7 +81,7 @@ export function importFetchedStatuses(statuses) { } if (status.poll && status.poll.id) { - pushUnique(polls, normalizePoll(status.poll)); + pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); } } @@ -95,7 +95,7 @@ export function importFetchedStatuses(statuses) { } export function importFetchedPoll(poll) { - return dispatch => { - dispatch(importPolls([normalizePoll(poll)])); + return (dispatch, getState) => { + dispatch(importPolls([normalizePoll(poll, getState().getIn(['polls', poll.id]))])); }; } diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index d5daef4193..64d98a29db 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -77,6 +77,7 @@ export function normalizeStatus(status, normalOldStatus, settings) { normalStatus.hidden = normalOldStatus.get('hidden'); normalStatus.quote = normalOldStatus.get('quote'); normalStatus.quote_hidden = normalOldStatus.get('quote_hidden'); + normalStatus.translation = normalOldStatus.get('translation'); } else { const spoilerText = normalStatus.spoiler_text || ''; const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); @@ -117,6 +118,18 @@ export function normalizeStatus(status, normalOldStatus, settings) { } } + if (normalOldStatus) { + const list = normalOldStatus.get('media_attachments'); + if (normalStatus.media_attachments && list) { + normalStatus.media_attachments.forEach(item => { + const oldItem = list.find(i => i.get('id') === item.id); + if (oldItem && oldItem.get('description') === item.description) { + item.translation = oldItem.get('translation') + } + }); + } + } + return normalStatus; } @@ -135,15 +148,23 @@ export function normalizeStatusTranslation(translation, status) { return normalTranslation; } -export function normalizePoll(poll) { +export function normalizePoll(poll, normalOldPoll) { const normalPoll = { ...poll }; const emojiMap = makeEmojiMap(poll.emojis); - normalPoll.options = poll.options.map((option, index) => ({ - ...option, - voted: poll.own_votes && poll.own_votes.includes(index), - titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap), - })); + normalPoll.options = poll.options.map((option, index) => { + const normalOption = { + ...option, + voted: poll.own_votes && poll.own_votes.includes(index), + titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap), + } + + if (normalOldPoll && normalOldPoll.getIn(['options', index, 'title']) === option.title) { + normalOption.translation = normalOldPoll.getIn(['options', index, 'translation']); + } + + return normalOption + }); return normalPoll; } diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index d5154c6a84..33cf376a26 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -15,6 +15,9 @@ export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST'; export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; +export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK'; +export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET'; + export function changeSearch(value) { return { type: SEARCH_CHANGE, @@ -28,7 +31,7 @@ export function clearSearch() { }; } -export function submitSearch() { +export function submitSearch(type) { return (dispatch, getState) => { const value = getState().getIn(['search', 'value']); const signedIn = !!getState().getIn(['meta', 'me']); @@ -45,6 +48,7 @@ export function submitSearch() { q: value, resolve: signedIn, limit: 10, + type, }, }).then(response => { if (response.data.accounts) { @@ -131,3 +135,42 @@ export const expandSearchFail = error => ({ export const showSearch = () => ({ type: SEARCH_SHOW, }); + +export const openURL = routerHistory => (dispatch, getState) => { + const value = getState().getIn(['search', 'value']); + const signedIn = !!getState().getIn(['meta', 'me']); + + if (!signedIn) { + return; + } + + dispatch(fetchSearchRequest()); + + api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { + if (response.data.accounts?.length > 0) { + dispatch(importFetchedAccounts(response.data.accounts)); + routerHistory.push(`/@${response.data.accounts[0].acct}`); + } else if (response.data.statuses?.length > 0) { + dispatch(importFetchedStatuses(response.data.statuses)); + routerHistory.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`); + } + + dispatch(fetchSearchSuccess(response.data, value)); + }).catch(err => { + dispatch(fetchSearchFail(err)); + }); +}; + +export const clickSearchResult = (q, type) => ({ + type: SEARCH_RESULT_CLICK, + + result: { + type, + q, + }, +}); + +export const forgetSearchResult = q => ({ + type: SEARCH_RESULT_FORGET, + q, +}); diff --git a/app/javascript/flavours/glitch/blurhash.ts b/app/javascript/flavours/glitch/blurhash.ts index dadf2b7f2c..cafe7b12dc 100644 --- a/app/javascript/flavours/glitch/blurhash.ts +++ b/app/javascript/flavours/glitch/blurhash.ts @@ -86,10 +86,9 @@ const DIGIT_CHARACTERS = [ export const decode83 = (str: string) => { let value = 0; - let c, digit; + let digit; - for (let i = 0; i < str.length; i++) { - c = str[i]; + for (const c of str) { digit = DIGIT_CHARACTERS.indexOf(c); value = value * 83 + digit; } diff --git a/app/javascript/flavours/glitch/components/animated_number.tsx b/app/javascript/flavours/glitch/components/animated_number.tsx index 81e0af395b..2beb6efbe4 100644 --- a/app/javascript/flavours/glitch/components/animated_number.tsx +++ b/app/javascript/flavours/glitch/components/animated_number.tsx @@ -5,7 +5,7 @@ import { TransitionMotion, spring } from 'react-motion'; import { reduceMotion } from '../initial_state'; -import ShortNumber from './short_number'; +import { ShortNumber } from './short_number'; const obfuscatedCount = (count: number) => { if (count < 0) { @@ -33,7 +33,7 @@ export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]); const willLeave = useCallback( () => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), - [direction] + [direction], ); if (reduceMotion) { diff --git a/app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx b/app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx index 932370884a..6da6200142 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx +++ b/app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx @@ -1,16 +1,16 @@ import { FormattedMessage } from 'react-intl'; -import ShortNumber from 'flavours/glitch/components/short_number'; +import { ShortNumber } from 'flavours/glitch/components/short_number'; interface Props { tag: { name: string; url?: string; - history?: Array<{ + history?: { uses: number; accounts: string; day: string; - }>; + }[]; following?: boolean; type: 'hashtag'; }; diff --git a/app/javascript/flavours/glitch/components/avatar.tsx b/app/javascript/flavours/glitch/components/avatar.tsx index 11253a8e94..92d8a135b0 100644 --- a/app/javascript/flavours/glitch/components/avatar.tsx +++ b/app/javascript/flavours/glitch/components/avatar.tsx @@ -33,7 +33,7 @@ export const Avatar: React.FC = ({ if (account) { style.backgroundImage = `url(${account.get( - hovering ? 'avatar' : 'avatar_static' + hovering ? 'avatar' : 'avatar_static', )})`; } @@ -42,7 +42,7 @@ export const Avatar: React.FC = ({ className={classNames( 'account__avatar', { 'account__avatar-inline': inline }, - className + className, )} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} diff --git a/app/javascript/flavours/glitch/components/column_back_button.jsx b/app/javascript/flavours/glitch/components/column_back_button.jsx index 0934d4b335..5a11a3d5ea 100644 --- a/app/javascript/flavours/glitch/components/column_back_button.jsx +++ b/app/javascript/flavours/glitch/components/column_back_button.jsx @@ -20,9 +20,7 @@ export default class ColumnBackButton extends PureComponent { handleClick = () => { const { router } = this.context; - // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 - // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location - if (router.route.location.key) { + if (router.history.location?.state?.fromMastodon) { router.history.goBack(); } else { router.history.push('/'); diff --git a/app/javascript/flavours/glitch/components/column_header.jsx b/app/javascript/flavours/glitch/components/column_header.jsx index e8c056c0bf..8a68036e9c 100644 --- a/app/javascript/flavours/glitch/components/column_header.jsx +++ b/app/javascript/flavours/glitch/components/column_header.jsx @@ -65,9 +65,7 @@ class ColumnHeader extends PureComponent { handleBackClick = () => { const { router } = this.context; - // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 - // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location - if (router.route.location.key) { + if (router.history.location?.state?.fromMastodon) { router.history.goBack(); } else { router.history.push('/'); @@ -87,6 +85,7 @@ class ColumnHeader extends PureComponent { }; render () { + const { router } = this.context; const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; const { collapsed, animating } = this.state; @@ -130,7 +129,7 @@ class ColumnHeader extends PureComponent { pinButton = ; } - if (!pinned && (multiColumn || showBackButton)) { + if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { backButton = (