Compare commits

..

30 Commits
main ... v4.5.0

Author SHA1 Message Date
Claire
26c78392f8
Bump version to v4.5.0 (#36732)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
CSS Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
Ruby Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Historical data migration test / test (14-alpine) (push) Has been cancelled
Historical data migration test / test (15-alpine) (push) Has been cancelled
Historical data migration test / test (16-alpine) (push) Has been cancelled
Historical data migration test / test (17-alpine) (push) Has been cancelled
2025-11-06 12:39:07 +01:00
github-actions[bot]
048430f4e8
New Crowdin Translations for stable-4.5 (automated) (#36745)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-11-06 11:37:54 +01:00
Echo
d45b4db1d7 Fix: correctly dismisses announcement when viewed (#36750) 2025-11-06 11:23:46 +01:00
Echo
ef3a95affc Add default visualizer for audio upload without poster (#36734) 2025-11-06 10:34:12 +01:00
diondiondion
3e6a9371b0 Fix spoiler toggle button being able to submit compose form (#36736) 2025-11-06 10:34:12 +01:00
Claire
e91c764590 Bump version to v4.5.0-rc.3
Some checks failed
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Bundler Audit / security (push) Has been cancelled
Crowdin / Upload translations / upload-translations (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
2025-11-05 09:59:00 +01:00
Claire
cfdd9396c0 Change paste-link-to-quote loading state from generic loading bar to compose placeholder (#36695) 2025-11-05 09:59:00 +01:00
Claire
ba498ae779 Change quote action to error instead of insert link in Private Mentions (#36721) 2025-11-05 09:59:00 +01:00
Echo
5bae08d1ff Quote Posts: Add notifications for DMs and private posts (#36696) 2025-11-05 09:59:00 +01:00
Echo
5253527ec4 Add CSS Module support (#36637) 2025-11-05 09:59:00 +01:00
Claire
0b50789c5b Fix Skeleton placeholders being animated when setting to reduce animations is enabled (#36716) 2025-11-05 09:59:00 +01:00
Claire
a978e37f4c Fix quote dropdown menu item in detailed status view (#36704) 2025-11-05 09:59:00 +01:00
Claire
dd708298a8 Remove option to disable access to local topic feeds for logged-in users (#36703) 2025-11-05 09:59:00 +01:00
renovate[bot]
449eb03f11 chore(deps): update dependency sidekiq to v8.0.9 (#36699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 09:59:00 +01:00
renovate[bot]
1baede0a7c chore(deps): update dependency brakeman to v7.1.1 (#35434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 09:59:00 +01:00
renovate[bot]
a7ecfc1ca5 fix(deps): update dependency @rails/ujs to v7.1.600 (#36634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 09:59:00 +01:00
Claire
e62baacfc1 Increase number of quote approval job retries (#36698) 2025-11-05 09:59:00 +01:00
Rachael Wright-Munn
b5a6feb3bf Move "Privacy and reach" from "Public profile" to top-level navigation (#27294) 2025-11-05 09:59:00 +01:00
Claire
05964f571b Prevent creation of Private Mentions quoting someone who is not mentioned (#36689) 2025-11-05 09:59:00 +01:00
Claire
16a54f7158 Fix issuance of quote approval for remote private statuses (#36693) 2025-11-05 09:59:00 +01:00
Claire
6d53ca63d6 Disable paste-link-to-quote flow when composing Private Mentions (#36690) 2025-11-05 09:59:00 +01:00
renovate[bot]
93acfdd7d3 chore(deps): update dependency irb to v1.15.3 (#36682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 09:59:00 +01:00
renovate[bot]
a209b8e544 chore(deps): update dependency rubyzip to v3.2.2 (#36687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 09:59:00 +01:00
Claire
af4c372ab2 Bump version to v4.5.0-rc.2
Some checks failed
Check i18n / check-i18n (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (ruby) (push) Has been cancelled
Crowdin / Upload translations / upload-translations (push) Has been cancelled
Check formatting / lint (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
Ruby Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Historical data migration test / test (14-alpine) (push) Has been cancelled
Historical data migration test / test (15-alpine) (push) Has been cancelled
Historical data migration test / test (16-alpine) (push) Has been cancelled
Historical data migration test / test (17-alpine) (push) Has been cancelled
Ruby Testing / build (production) (push) Has been cancelled
Ruby Testing / build (test) (push) Has been cancelled
Ruby Testing / test (.ruby-version) (push) Has been cancelled
Ruby Testing / test (3.2) (push) Has been cancelled
Ruby Testing / test (3.3) (push) Has been cancelled
Ruby Testing / ImageMagick tests (.ruby-version) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.2) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.3) (push) Has been cancelled
Ruby Testing / End to End testing (.ruby-version) (push) Has been cancelled
Ruby Testing / End to End testing (3.2) (push) Has been cancelled
Ruby Testing / End to End testing (3.3) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
2025-10-31 16:01:06 +01:00
diondiondion
aa579ce286 Fix initially selected language in Rules panel, hide selector when no alternative translations exist (#36672) 2025-10-31 16:01:06 +01:00
github-actions[bot]
adfabf8c80
New Crowdin Translations for stable-4.5 (automated) (#36670)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-10-31 14:51:18 +01:00
renovate[bot]
ea710df180 chore(deps): update dependency axios to v1.13.1 (#36633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 14:09:36 +01:00
renovate[bot]
e1b6e28829 chore(deps): update dependency libvips to v8.17.3 (#36654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 14:09:36 +01:00
diondiondion
214d59bd37 Show error when submitting empty post rather than failing silently (#36650) 2025-10-31 14:09:36 +01:00
Claire
e4291e9b05 Fix SMTP configuration with mail 2.9.0 (#36646) 2025-10-31 14:09:36 +01:00
2031 changed files with 25297 additions and 66547 deletions

View File

@ -1,5 +1,5 @@
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby
FROM mcr.microsoft.com/devcontainers/ruby:3.4-trixie
FROM mcr.microsoft.com/devcontainers/ruby:1-3.3-bookworm
# Install node version from .nvmrc
WORKDIR /app
@ -9,7 +9,7 @@ RUN /bin/bash --login -i -c "nvm install"
# Install additional OS packages
RUN apt-get update && \
export DEBIAN_FRONTEND=noninteractive && \
apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg libvips42 libpam-dev
apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libvips42 libpam-dev
# Disable download prompt for Corepack
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0

View File

@ -56,7 +56,7 @@ services:
- internal_network
es:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
restart: unless-stopped
environment:
ES_JAVA_OPTS: -Xms512m -Xmx512m
@ -73,7 +73,7 @@ services:
hard: -1
libretranslate:
image: libretranslate/libretranslate:v1.7.3
image: libretranslate/libretranslate:v1.6.2
restart: unless-stopped
volumes:
- lt-data:/home/libretranslate/.local

View File

@ -59,7 +59,7 @@ body:
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.4.9)
- Ruby version: (from `ruby --version`, eg. v3.4.4)
- Node.js version: (from `node --version`, eg. v22.16.0)
validations:
required: false

View File

@ -61,7 +61,7 @@ body:
value: |
Please at least include those informations:
- Operating system: (eg. Ubuntu 24.04.2)
- Ruby version: (from `ruby --version`, eg. v3.4.9)
- Ruby version: (from `ruby --version`, eg. v3.4.4)
- Node.js version: (from `node --version`, eg. v22.16.0)
validations:
required: false

View File

@ -9,7 +9,7 @@ runs:
using: 'composite'
steps:
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@ -23,7 +23,7 @@ runs:
shell: bash
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
- uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@ -17,7 +17,7 @@ runs:
sudo apt-get install -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }}
- name: Set up Ruby
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ inputs.ruby-version }}
bundler-cache: true

View File

@ -5,6 +5,7 @@
'customManagers:dockerfileVersions',
':labels(dependencies)',
':prConcurrentLimitNone', // Remove limit for open PRs at any time.
':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour.
':enableVulnerabilityAlertsWithLabel(security)',
],
rebaseWhen: 'conflicted',
@ -22,6 +23,8 @@
// Require Dependency Dashboard Approval for major version bumps of these node packages
matchManagers: ['npm'],
matchPackageNames: [
'tesseract.js', // Requires code changes
// react-router: Requires manual upgrade
'history',
'react-router-dom',
@ -113,7 +116,6 @@
],
matchUpdateTypes: ['major'],
groupName: 'artifact actions (major)',
extends: ['helpers:pinGitHubActionDigests'],
},
{
// Update @types/* packages every week, with one grouped PR
@ -154,15 +156,9 @@
groupName: 'opentelemetry-ruby (non-major)',
},
{
// The ruby portion of the Playwright group
matchManagers: ['bundler'],
matchPackageNames: ['playwright-ruby-client'],
groupName: 'Playwright',
},
{
// The node portion of the Playwright group
matchManagers: ['npm'],
matchPackageNames: ['playwright'],
// Group Playwright Ruby & JS deps in the same PR, as they need to be in sync
matchManagers: ['bundler', 'npm'],
matchPackageNames: ['playwright-ruby-client', 'playwright'],
groupName: 'Playwright',
},
// Add labels depending on package manager

View File

@ -35,7 +35,7 @@ jobs:
- linux/arm64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- name: Prepare
env:
@ -47,19 +47,19 @@ jobs:
image_names=${PUSH_TO_IMAGES//$'\n'/,}
echo "IMAGE_NAMES=${image_names%,}" >> $GITHUB_ENV
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- uses: docker/setup-buildx-action@v3
id: buildx
- name: Log in to Docker Hub
if: contains(inputs.push_to_images, 'tootsuite')
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
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@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -67,7 +67,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
uses: docker/metadata-action@v5
if: ${{ inputs.push_to_images != '' }}
with:
images: ${{ inputs.push_to_images }}
@ -76,7 +76,7 @@ jobs:
- name: Build and push by digest
id: build
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
uses: docker/build-push-action@v6
with:
context: .
file: ${{ inputs.file_to_build }}
@ -100,7 +100,7 @@ jobs:
- name: Upload digest
if: ${{ inputs.push_to_images != '' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@v4
with:
# `hashFiles` is used to disambiguate between streaming and non-streaming images
name: digests-${{ hashFiles(inputs.file_to_build) }}-${{ env.PLATFORM_PAIR }}
@ -119,10 +119,10 @@ jobs:
PUSH_TO_IMAGES: ${{ inputs.push_to_images }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
# `hashFiles` is used to disambiguate between streaming and non-streaming images
@ -131,25 +131,25 @@ jobs:
- name: Log in to Docker Hub
if: contains(inputs.push_to_images, 'tootsuite')
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
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@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
uses: docker/metadata-action@v5
if: ${{ inputs.push_to_images != '' }}
with:
images: ${{ inputs.push_to_images }}

View File

@ -18,7 +18,7 @@ jobs:
steps:
# Repository needs to be cloned so `git rev-parse` below works
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- id: version_vars
run: |
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT

View File

@ -9,44 +9,7 @@ permissions:
packages: write
jobs:
check-latest-stable:
runs-on: ubuntu-latest
outputs:
latest: ${{ steps.check.outputs.is_latest_stable }}
steps:
# Repository needs to be cloned to list branches
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Check latest stable
shell: bash
id: check
run: |
ref="${GITHUB_REF#refs/tags/}"
if [[ "$ref" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]]; then
current="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
else
echo "tag $ref is not semver"
echo "is_latest_stable=false" >> "$GITHUB_OUTPUT"
exit 0
fi
latest=$(git for-each-ref --format='%(refname:short)' "refs/remotes/origin/stable-*.*" \
| sed -E 's#^origin/stable-##' \
| sort -Vr \
| head -n1)
if [[ "$current" == "$latest" ]]; then
echo "is_latest_stable=true" >> "$GITHUB_OUTPUT"
else
echo "is_latest_stable=false" >> "$GITHUB_OUTPUT"
fi
build-image:
needs: check-latest-stable
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
@ -58,14 +21,13 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ needs.check-latest-stable.outputs.latest }}
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit
build-image-streaming:
needs: check-latest-stable
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
@ -77,7 +39,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ needs.check-latest-stable.outputs.latest }}
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}

View File

@ -28,10 +28,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
@ -42,7 +42,8 @@ jobs:
- name: Check for missing strings in English YML
run: |
bin/i18n-tasks missing -t used -l en
bin/i18n-tasks add-missing -l en
git diff --exit-code
- name: Check for wrong string interpolations
run: bin/i18n-tasks check-consistent-interpolations

View File

@ -1,51 +1,31 @@
name: 'Chromatic'
permissions:
contents: read
on:
push:
branches-ignore:
- renovate/*
- stable-*
paths:
- 'package.json'
- 'yarn.lock'
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
- '**/*.tsx'
- '**/*.css'
- '**/*.scss'
- '.github/workflows/chromatic.yml'
jobs:
pathcheck:
name: Check for relevant changes
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.filter.outputs.src }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
id: filter
with:
filters: |
src:
- 'package.json'
- 'yarn.lock'
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
- '**/*.tsx'
- '**/*.css'
- '**/*.scss'
- '.github/workflows/chromatic.yml'
chromatic:
name: Run Chromatic
runs-on: ubuntu-latest
needs: pathcheck
if: github.repository == 'mastodon/mastodon' && needs.pathcheck.outputs.changed == 'true'
if: github.repository == 'mastodon/mastodon'
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
@ -53,10 +33,9 @@ jobs:
run: yarn build-storybook
- name: Run Chromatic
uses: chromaui/action@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0 # v13
uses: chromaui/action@v12
with:
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
zip: true
storybookBuildDir: 'storybook-static'
exitOnceUploaded: true # Exit immediately after upload
autoAcceptChanges: 'main' # Auto-accept changes on main branch only

View File

@ -31,11 +31,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -61,6 +61,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4
uses: github/codeql-action/analyze@v3
with:
category: '/language:${{matrix.language}}'

View File

@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Increase Git http.postBuffer
# This is needed due to a bug in Ubuntu's cURL version?
@ -24,7 +24,7 @@ jobs:
# Download the translation files from Crowdin
- name: crowdin action
uses: crowdin/github-action@b4b468cffefb50bdd99dd83e5d2eaeb63c880380 # v2
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
@ -50,7 +50,7 @@ jobs:
# Create or update the pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
uses: peter-evans/create-pull-request@v7.0.8
with:
commit-message: 'New Crowdin translations'
title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)'

View File

@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Increase Git http.postBuffer
# This is needed due to a bug in Ubuntu's cURL version?
@ -26,7 +26,7 @@ jobs:
# Download the translation files from Crowdin
- name: crowdin action
uses: crowdin/github-action@b4b468cffefb50bdd99dd83e5d2eaeb63c880380 # v2
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
@ -52,7 +52,7 @@ jobs:
# Create or update the pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
uses: peter-evans/create-pull-request@v7
with:
commit-message: 'New Crowdin translations'
title: 'New Crowdin Translations (automated)'

View File

@ -23,10 +23,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: crowdin action
uses: crowdin/github-action@b4b468cffefb50bdd99dd83e5d2eaeb63c880380 # v2
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false

View File

@ -13,10 +13,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
- name: Check formatting
- name: Check formatting with Prettier
run: yarn format:check

View File

@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "haml-lint",
"severity": "warning",
"pattern": [
{
"regexp": "^(.*):(\\d+)\\s\\[W]\\s(.*):\\s(.*)$",
"file": 1,
"line": 2,
"code": 3,
"message": 4
}
]
}
]
}

View File

@ -9,6 +9,7 @@ on:
- 'package.json'
- 'yarn.lock'
- '.nvmrc'
- '.prettier*'
- 'stylelint.config.js'
- '**/*.css'
- '**/*.scss'
@ -20,6 +21,7 @@ on:
- 'package.json'
- 'yarn.lock'
- '.nvmrc'
- '.prettier*'
- 'stylelint.config.js'
- '**/*.css'
- '**/*.scss'
@ -32,7 +34,7 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

View File

@ -33,13 +33,14 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Run haml-lint
run: |
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
bin/haml-lint --reporter github

View File

@ -10,6 +10,7 @@ on:
- 'yarn.lock'
- 'tsconfig.json'
- '.nvmrc'
- '.prettier*'
- 'eslint.config.mjs'
- '**/*.js'
- '**/*.jsx'
@ -23,6 +24,7 @@ on:
- 'yarn.lock'
- 'tsconfig.json'
- '.nvmrc'
- '.prettier*'
- 'eslint.config.mjs'
- '**/*.js'
- '**/*.jsx'
@ -36,7 +38,7 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

View File

@ -35,15 +35,15 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set-up RuboCop Problem Matcher
uses: r7kamura/rubocop-problem-matchers-action@59f1a0759f50cc2649849fd850b8487594bb5a81 # v1.2.2
uses: r7kamura/rubocop-problem-matchers-action@v1
- name: Run rubocop
run: bin/rubocop

View File

@ -18,7 +18,7 @@ jobs:
steps:
- name: Check for merge conflicts
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3
uses: eps1lon/actions-label-merge-conflict@v3
with:
dirtyLabel: 'rebase needed :construction:'
repoToken: '${{ secrets.GITHUB_TOKEN }}'

View File

@ -34,7 +34,7 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

View File

@ -72,7 +72,7 @@ jobs:
BUNDLE_RETRY: 3
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

View File

@ -32,7 +32,7 @@ jobs:
SECRET_KEY_BASE_DUMMY: 1
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
@ -43,7 +43,7 @@ jobs:
onlyProduction: 'true'
- name: Cache assets from compilation
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
uses: actions/cache@v4
with:
path: |
public/assets
@ -65,7 +65,7 @@ jobs:
run: |
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tmp/cache/vite/last-build*.json
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
- uses: actions/upload-artifact@v4
if: matrix.mode == 'test'
with:
path: |-
@ -128,9 +128,9 @@ jobs:
- '3.3'
- '.ruby-version'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
- uses: actions/download-artifact@v4
with:
path: './'
name: ${{ github.sha }}
@ -151,7 +151,7 @@ jobs:
bin/flatware fan bin/rails db:test:prepare
- name: Cache RSpec persistence file
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
uses: actions/cache@v4
with:
path: |
tmp/rspec/examples.txt
@ -167,12 +167,99 @@ jobs:
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
uses: codecov/codecov-action@v5
with:
files: coverage/lcov/*.lcov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-imagemagick:
name: ImageMagick tests
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 10ms
--health-timeout 3s
--health-retries 50
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10ms
--health-timeout 3s
--health-retries 50
ports:
- 6379:6379
env:
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }}
RAILS_ENV: test
ALLOW_NOPAM: true
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'
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
MASTODON_USE_LIBVIPS: false
strategy:
fail-fast: false
matrix:
ruby-version:
- '3.2'
- '3.3'
- '.ruby-version'
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: './'
name: ${{ github.sha }}
- name: Expand archived asset artifacts
run: |
tar xvzf artifacts.tar.gz
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick libpam-dev
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bin/rspec --tag attachment_processing
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@v5
with:
files: coverage/lcov/mastodon.lcov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-e2e:
name: End to End testing
runs-on: ubuntu-latest
@ -222,9 +309,9 @@ jobs:
- '.ruby-version'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
- uses: actions/download-artifact@v4
with:
path: './'
name: ${{ github.sha }}
@ -247,7 +334,7 @@ jobs:
- name: Cache Playwright Chromium browser
id: playwright-cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
@ -263,14 +350,14 @@ jobs:
- run: bin/rspec spec/system --tag streaming --tag js
- name: Archive logs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-logs-${{ matrix.ruby-version }}
path: log/
- name: Archive test screenshots
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-screenshots-${{ matrix.ruby-version }}
@ -352,17 +439,17 @@ jobs:
- '3.3'
- '.ruby-version'
search-image:
- docker.elastic.co/elasticsearch/elasticsearch:7.17.29
- docker.elastic.co/elasticsearch/elasticsearch:7.17.13
include:
- ruby-version: '.ruby-version'
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.19.2
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
- ruby-version: '.ruby-version'
search-image: opensearchproject/opensearch:2
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v4
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
- uses: actions/download-artifact@v4
with:
path: './'
name: ${{ github.sha }}
@ -382,14 +469,14 @@ jobs:
- run: bin/rspec --tag search
- name: Archive logs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-search-logs-${{ matrix.ruby-version }}
path: log/
- name: Archive test screenshots
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-search-screenshots

View File

@ -10,6 +10,6 @@ linters:
MiddleDot:
enabled: true
LineLength:
max: 240 # Override default value of 80 inherited from rubocop
max: 300
ViewLength:
max: 200 # Override default value of 100 inherited from rubocop

2
.nvmrc
View File

@ -1 +1 @@
24.14
24.10

View File

@ -1,92 +0,0 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 80,
"ignorePatterns": [
"/tmp",
"/coverage",
"/public/assets",
"/public/emoji",
"/public/packs",
"/public/packs-test",
"/public/system",
"/public/vite*",
"*.html",
"docker-compose.override.yml",
// Ignore config YAML files that include ERB/ruby code
"config/email.yml",
// Vendored CSS
"app/javascript/styles/mastodon/reset.scss",
// Automatically generated
"/app/javascript/mastodon/features/emoji/emoji_map.json",
"/app/javascript/mastodon/features/emoji/emoji_data.json",
"AUTHORS.md",
"/app/javascript/mastodon/locales/*.json",
"/config/locales",
".storybook/static/mockServiceWorker.js",
// do not reformat JS files as this will change too many files and cause merge conflicts with open PRs and forks
"app/javascript/**/*.js",
"app/javascript/**/*.jsx",
"streaming/**/*.js"
],
"experimentalSortPackageJson": false,
"experimentalSortImports": {
"groups": [
["builtin"],
["react"],
["react-intl"],
["react-utils"],
["redux"],
["external", "type-external"],
["internal", "type-internal"],
["mastodon-internals"],
["parent", "type-parent"],
["sibling", "type-sibling", "index", "type-index"],
["side_effect"]
],
"customGroups": [
{
"groupName": "react",
"elementNamePattern": [
"react",
"react-dom",
"react-dom/client",
"prop-types"
]
},
{
"groupName": "react-intl",
"elementNamePattern": ["react-intl", "intl-messageformat"]
},
{
"groupName": "react-utils",
"elementNamePattern": [
"classnames",
"react-helmet",
"react-router",
"react-router-dom"
]
},
{
"groupName": "redux",
"elementNamePattern": [
"immutable",
"@reduxjs/toolkit",
"react-redux",
"react-immutable-proptypes",
"react-immutable-pure-component"
]
},
{
"groupName": "mastodon-internals",
"elementNamePattern": ["mastodon/**", "@/**"]
}
]
}
}

86
.prettierignore Normal file
View File

@ -0,0 +1,86 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config and downloaded libraries.
/.bundle
/vendor/bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
.eslintcache
/log/*
!/log/.keep
/tmp
/coverage
.env
.env.production
.env.development
/node_modules/
/build/
# Ignore Vagrant files
.vagrant/
# Ignore IDE files
.vscode/
.idea/
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
/postgres
/postgres14
/redis
/elasticsearch
# Ignore Apple files
.DS_Store
# Ignore vim files
*~
*.swp
# Ignore log files
*.log
# Ignore Docker option files
docker-compose.override.yml
# Ignore public
/public/assets
/public/emoji
/public/packs
/public/packs-test
/public/system
/public/vite*
# Ignore emoji map file
/app/javascript/mastodon/features/emoji/emoji_map.json
/app/javascript/mastodon/features/emoji/emoji_data.json
# Ignore locale files
/app/javascript/mastodon/locales/*.json
/config/locales
# Ignore vendored CSS reset
app/javascript/styles/mastodon/reset.scss
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
*.js
*.jsx
# Ignore HTML till cleaned and included in CI
*.html
# Ignore the generated AUTHORS.md
AUTHORS.md
# Process a few selected JS files
!lint-staged.config.js
# Ignore config YAML files that include ERB/ruby code prettier does not understand
/config/email.yml

4
.prettierrc.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
singleQuote: true,
jsxSingleQuote: true
};

View File

@ -4,6 +4,3 @@ Layout/FirstHashElementIndentation:
Layout/LineLength:
Max: 300 # Default of 120 causes a duplicate entry in generated todo file
Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented

View File

@ -1 +1 @@
3.4.9
3.4.7

View File

@ -27,12 +27,11 @@ const config: StorybookConfig = {
'oops.gif',
'oops.png',
].map((path) => ({ from: `../public/${path}`, to: `/${path}` })),
{ from: '../app/javascript/images/logo.svg', to: '/custom-emoji/logo.svg' },
],
viteFinal(config) {
// For an unknown reason, Storybook does not use the root
// from the Vite config so we need to set it manually.
config.root = resolve(import.meta.dirname, '../app/javascript');
config.root = resolve(__dirname, '../app/javascript');
return config;
},
};

View File

@ -1,8 +0,0 @@
export const modes = {
darkTheme: {
theme: 'dark',
},
lightTheme: {
theme: 'light',
},
} as const;

View File

@ -1,2 +1,2 @@
<html class="no-reduce-motion" data-color-scheme="light">
<html class="no-reduce-motion">
</html>

View File

@ -11,19 +11,14 @@ import type { Preview } from '@storybook/react-vite';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { action } from 'storybook/actions';
import {
importCustomEmojiData,
importLegacyShortcodes,
importEmojiData,
} from '@/mastodon/features/emoji/loader';
import type { LocaleData } from '@/mastodon/locales';
import { reducerWithInitialState } from '@/mastodon/reducers';
import { defaultMiddleware } from '@/mastodon/store/store';
import { mockHandlers, unhandledRequestHandler } from '@/testing/api';
import { modes } from './modes';
import '../app/javascript/styles/application.scss';
// If you want to run the dark theme during development,
// you can change the below to `/application.scss`
import '../app/javascript/styles/mastodon-light.scss';
import './styles.css';
const localeFiles = import.meta.glob('@/mastodon/locales/*.json', {
@ -50,46 +45,17 @@ const preview: Preview = {
dynamicTitle: true,
},
},
theme: {
description: 'Theme for the story',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: [{ value: 'light' }, { value: 'dark' }],
dynamicTitle: true,
},
},
},
initialGlobals: {
locale: 'en',
theme: 'light',
},
decorators: [
(Story, { parameters, globals, args, argTypes }) => {
(Story, { parameters, globals, args }) => {
// Get the locale from the global toolbar
// and merge it with any parameters or args state.
const { locale } = globals as { locale: string };
const { state = {} } = parameters;
const argsState: Record<string, unknown> = {};
for (const [key, value] of Object.entries(args)) {
const argType = argTypes[key];
if (argType?.reduxPath) {
const reduxPath = Array.isArray(argType.reduxPath)
? argType.reduxPath.map((p) => p.toString())
: argType.reduxPath.split('.');
reduxPath.reduce((acc, key, i) => {
if (acc[key] === undefined) {
acc[key] = {};
}
if (i === reduxPath.length - 1) {
acc[key] = value;
}
return acc[key] as Record<string, unknown>;
}, argsState);
}
}
const { state: argsState = {} } = args;
const reducer = reducerWithInitialState(
{
@ -98,7 +64,7 @@ const preview: Preview = {
},
},
state as Record<string, unknown>,
argsState,
argsState as Record<string, unknown>,
);
const store = configureStore({
@ -145,13 +111,6 @@ const preview: Preview = {
</IntlProvider>
);
},
(Story, { globals }) => {
const theme = (globals.theme as string) || 'light';
useEffect(() => {
document.body.setAttribute('data-color-scheme', theme);
}, [theme]);
return <Story />;
},
(Story) => (
<MemoryRouter>
<Story />
@ -168,12 +127,7 @@ const preview: Preview = {
</MemoryRouter>
),
],
loaders: [
mswLoader,
importCustomEmojiData,
importLegacyShortcodes,
({ globals: { locale } }) => importEmojiData(locale as string),
],
loaders: [mswLoader],
parameters: {
layout: 'centered',
@ -198,13 +152,6 @@ const preview: Preview = {
msw: {
handlers: mockHandlers,
},
chromatic: {
modes: {
dark: modes.darkTheme,
light: modes.lightTheme,
},
},
},
};

View File

@ -7,7 +7,7 @@
* - Please do NOT modify this file.
*/
const PACKAGE_VERSION = '2.12.1'
const PACKAGE_VERSION = '2.11.3'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
@ -205,7 +205,6 @@ async function resolveMainClient(event) {
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId, requestInterceptedAt) {

View File

@ -1,20 +1,7 @@
// The addon package.json incorrectly exports types, so we need to override them here.
import type { RootState } from '@/mastodon/store';
// See: https://github.com/storybookjs/storybook/blob/v9.0.4/code/addons/vitest/package.json#L70-L76
declare module '@storybook/addon-vitest/vitest-plugin' {
export * from '@storybook/addon-vitest/dist/vitest-plugin/index';
}
type RootPathKeys = keyof RootState;
declare module 'storybook/internal/csf' {
export interface InputType {
reduxPath?:
| `${RootPathKeys}.${string}`
| [RootPathKeys, ...(string | number)[]];
}
}
export {};

View File

@ -0,0 +1,13 @@
diff --git a/lib/index.js b/lib/index.js
index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -99,7 +99,7 @@ function lodash(_ref) {
var node = _ref3;
- if ((0, _types.isModuleDeclaration)(node)) {
+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) {
isModule = true;
break;
}

View File

@ -538,7 +538,7 @@ and provided thanks to the work of the following contributors:
* [Drew Schuster](mailto:dtschust@gmail.com)
* [Dryusdan](mailto:dryusdan@dryusdan.fr)
* [Eai](mailto:eai@mizle.net)
* [Eashwar Ranganathan](mailto:eashwar@eashwar.com)
* [Eashwar Ranganathan](mailto:eranganathan@lyft.com)
* [Ed Knutson](mailto:knutsoned@gmail.com)
* [Elizabeth Martín Campos](mailto:me@elizabeth.sh)
* [Elizabeth Myers](mailto:elizabeth@interlinked.me)

File diff suppressed because it is too large Load Diff

View File

@ -11,8 +11,6 @@ You can contribute in the following ways:
Please review the org-level [contribution guidelines] for high-level acceptance
criteria guidance and the [DEVELOPMENT] guide for environment-specific details.
You should also read the project's [AI Contribution Policy] to understand how we approach
AI-assisted contributions.
## API Changes and Additions
@ -43,7 +41,7 @@ reviewed and merged into the codebase.
Our time is limited and PRs making large, unsolicited changes are unlikely to
get a response. Changes which link to an existing confirmed issue, or which come
from a "help wanted" issue or other request, are more likely to be reviewed.
from a "help wanted" issue or other request are more likely to be reviewed.
The smaller and more narrowly focused the changes in a PR are, the easier they
are to review and potentially merge. If the change only makes sense in some
@ -91,4 +89,3 @@ and API docs. Improvements are made via PRs to the [documentation repository].
[keepachangelog]: https://keepachangelog.com/en/1.0.0/
[Mastodon documentation]: https://docs.joinmastodon.org
[SECURITY]: SECURITY.md
[AI Contribution Policy]: https://github.com/mastodon/.github/blob/main/AI_POLICY.md

View File

@ -13,7 +13,7 @@ ARG BASE_REGISTRY="docker.io"
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
# renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.4.9"
ARG RUBY_VERSION="3.4.7"
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="22"]
# renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="24"
@ -70,6 +70,8 @@ ENV \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
# Optimize jemalloc 5.x performance
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
# Enable libvips, should not be changed
MASTODON_USE_LIBVIPS=true \
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
@ -181,7 +183,7 @@ FROM build AS libvips
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
ARG VIPS_VERSION=8.18.1
ARG VIPS_VERSION=8.17.3
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download

View File

@ -13,8 +13,7 @@
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
- [FEP-8fcf: Followers collection synchronization across servers](https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md)
- [FEP-5feb: Search indexing consent for actors](https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md)
- [FEP-044f: Consent-respecting quote posts](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md)
- [FEP-3b86: Activity Intents](https://codeberg.org/fediverse/fep/src/branch/main/fep/3b86/fep-3b86.md): offer handlers for `Object` and `Create` (with support for the `content` parameter only), has support for the `Follow`, `Announce`, `Like` and `Object` intents
- [FEP-044f: Consent-respecting quote posts](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md): partial support for incoming quote-posts
## ActivityPub in Mastodon
@ -49,25 +48,3 @@ Mastodon requires all `POST` requests to be signed, and MAY require `GET` reques
### Additional documentation
- [Mastodon documentation](https://docs.joinmastodon.org/)
## Size limits
Mastodon imposes a few hard limits on federated content.
These limits are intended to be very generous and way above what the Mastodon user experience is optimized for, so as to accommodate future changes and unusual or unforeseen usage patterns, while still providing some limits for performance reasons.
The following table summarizes those limits.
| Limited property | Size limit | Consequence of exceeding the limit |
| ------------------------------------------------------------- | ---------- | ---------------------------------- |
| Serialized JSON-LD | 1MB | **Activity is rejected/dropped** |
| Profile fields (actor `PropertyValue` attachments) name/value | 2047 | Field name/value is truncated |
| Number of profile fields (actor `PropertyValue` attachments) | 50 | Fields list is truncated |
| Poll options (number of `anyOf`/`oneOf` in a `Question`) | 500 | Items list is truncated |
| Account username (actor `preferredUsername`) length | 2048 | **Actor will be rejected** |
| Account display name (actor `name`) length | 2048 | Display name will be truncated |
| Account note (actor `summary`) length | 20kB | Account note will be truncated |
| Account `attributionDomains` | 256 | List will be truncated |
| Account aliases (actor `alsoKnownAs`) | 256 | List will be truncated |
| Custom emoji shortcode (`Emoji` `name`) | 2048 | Emoji will be rejected |
| Media and avatar/header descriptions (`name`/`summary`) | 1500 | Description will be truncated |
| Collection name (`FeaturedCollection` `name`) | 256 | Name will be truncated |
| Collection description (`FeaturedCollection` `summary`) | 2048 | Description will be truncated |

47
Gemfile
View File

@ -5,7 +5,7 @@ ruby '>= 3.2.0', '< 3.5.0'
gem 'propshaft'
gem 'puma', '~> 7.0'
gem 'rails', '~> 8.1.0'
gem 'rails', '~> 8.0'
gem 'thor', '~> 1.2'
gem 'dotenv'
@ -13,7 +13,7 @@ gem 'haml-rails', '~>3.0'
gem 'pg', '~> 1.5'
gem 'pghero'
gem 'aws-sdk-core', require: false
gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873
gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'blurhash', '~> 0.1'
gem 'fog-core', '<= 2.6.0'
@ -24,11 +24,11 @@ gem 'ruby-vips', '~> 2.2', require: false
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', require: false
gem 'bootsnap', '~> 1.18.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy'
gem 'devise'
gem 'chewy', '~> 7.3'
gem 'devise', '~> 4.9'
gem 'devise-two-factor'
group :pam_authentication, optional: true do
@ -40,7 +40,7 @@ gem 'net-ldap', '~> 0.18'
gem 'omniauth', '~> 2.0'
gem 'omniauth-cas', '~> 3.0.0.beta.1'
gem 'omniauth_openid_connect', '~> 0.8.0'
gem 'omniauth-rails_csrf_protection', '~> 2.0'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
gem 'omniauth-saml', '~> 2.0'
gem 'color_diff', '~> 0.1'
@ -55,7 +55,7 @@ gem 'hiredis-client'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.3.0'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.8.0', require: false
gem 'httplog', '~> 1.7.0', require: false
gem 'i18n'
gem 'idn-ruby', require: 'idn'
gem 'inline_svg'
@ -67,10 +67,11 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
gem 'mutex_m'
gem 'nokogiri', '~> 1.15'
gem 'oj', '~> 3.14'
gem 'ox', '~> 2.14'
gem 'parslet'
gem 'premailer-rails'
gem 'public_suffix', '~> 7.0'
gem 'public_suffix', '~> 6.0'
gem 'pundit', '~> 2.3'
gem 'rack-attack', '~> 6.6'
gem 'rack-cors', require: 'rack/cors'
@ -95,28 +96,27 @@ gem 'tzinfo-data', '~> 1.2023'
gem 'webauthn', '~> 3.0'
gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913'
gem 'json'
gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.2'
gem 'rdf-normalize', '~> 0.5'
gem 'prometheus_exporter', '~> 2.2', require: false
gem 'opentelemetry-api', '~> 1.8.0'
gem 'opentelemetry-api', '~> 1.7.0'
group :opentelemetry do
gem 'opentelemetry-exporter-otlp', '~> 0.32.0', require: false
gem 'opentelemetry-exporter-otlp', '~> 0.31.0', require: false
gem 'opentelemetry-instrumentation-active_job', '~> 0.10.0', require: false
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.24.0', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.24.0', require: false
gem 'opentelemetry-instrumentation-excon', '~> 0.28.0', require: false
gem 'opentelemetry-instrumentation-faraday', '~> 0.32.0', require: false
gem 'opentelemetry-instrumentation-http', '~> 0.29.0', require: false
gem 'opentelemetry-instrumentation-http_client', '~> 0.28.0', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.28.0', require: false
gem 'opentelemetry-instrumentation-pg', '~> 0.35.0', require: false
gem 'opentelemetry-instrumentation-rack', '~> 0.30.0', require: false
gem 'opentelemetry-instrumentation-rails', '~> 0.40.0', require: false
gem 'opentelemetry-instrumentation-excon', '~> 0.26.0', require: false
gem 'opentelemetry-instrumentation-faraday', '~> 0.30.0', require: false
gem 'opentelemetry-instrumentation-http', '~> 0.27.0', require: false
gem 'opentelemetry-instrumentation-http_client', '~> 0.26.0', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.26.0', require: false
gem 'opentelemetry-instrumentation-pg', '~> 0.32.0', require: false
gem 'opentelemetry-instrumentation-rack', '~> 0.29.0', require: false
gem 'opentelemetry-instrumentation-rails', '~> 0.39.0', require: false
gem 'opentelemetry-instrumentation-redis', '~> 0.28.0', require: false
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.28.0', require: false
gem 'opentelemetry-sdk', '~> 1.4', require: false
@ -129,13 +129,16 @@ group :test do
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
gem 'rspec-github', '~> 3.0', require: false
# RSpec helpers for email specs
gem 'email_spec'
# Extra RSpec extension methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 5.0'
# Browser integration testing
gem 'capybara', '~> 3.39'
gem 'capybara-playwright-driver'
gem 'playwright-ruby-client', '1.57.1', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package
gem 'playwright-ruby-client', '1.55.0', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package
# Used to reset the database between system tests
gem 'database_cleaner-active_record'
@ -177,14 +180,14 @@ group :development do
# Enhanced error message pages for development
gem 'better_errors', '~> 2.9'
gem 'binding_of_caller'
gem 'binding_of_caller', '~> 1.0'
# Preview mail in the browser
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 3.0'
# Security analysis CLI tools
gem 'brakeman', '~> 8.0', require: false
gem 'brakeman', '~> 7.0', require: false
gem 'bundler-audit', '~> 0.9', require: false
# Linter CLI for HAML files

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,6 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub]
- **PostgreSQL** 14+
- **Redis** 7.0+
- **Node.js** 20+
- **FFmpeg** 5.1+
This repository includes deployment configurations for **Docker and docker-compose**, as well as for other environments like Heroku and Scalingo. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). A [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the main documentation.

View File

@ -15,7 +15,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
| Version | Supported |
| ------- | ---------------- |
| 4.5.x | Yes |
| 4.4.x | Yes |
| 4.3.x | Until 2026-05-06 |
| < 4.3 | No |
| 4.2.x | Until 2026-01-08 |
| < 4.2 | No |

1
Vagrantfile vendored
View File

@ -29,6 +29,7 @@ sudo apt-get install \
libpq-dev \
libxml2-dev \
libxslt1-dev \
imagemagick \
nodejs \
redis-server \
redis-tools \

View File

@ -53,9 +53,9 @@ class PublicStatusesIndex < Chewy::Index
}
index_scope ::Status.unscoped
.kept
.indexable
.includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card)
.kept
.indexable
.includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card)
root date_detection: false do
field(:id, type: 'long')

View File

@ -18,8 +18,6 @@ class AccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
redirect_to short_account_path(@account) if account_id_param.present? && username_param.blank?
end
format.rss do

View File

@ -1,36 +1,20 @@
# frozen_string_literal: true
class ActivityPub::CollectionsController < ActivityPub::BaseController
SUPPORTED_COLLECTIONS = %w(featured tags).freeze
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :check_authorization
before_action :set_items
before_action :set_size
before_action :set_type
def show
expires_in 3.minutes, public: public_fetch_mode?
if @unauthorized
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
else
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
end
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
end
private
def check_authorization
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
@unauthorized = authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
end
def set_items
case params[:id]
when 'featured'
@ -73,7 +57,11 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
end
def for_signed_account
if @unauthorized
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
[]
else
yield

View File

@ -36,8 +36,9 @@ class ActivityPub::ContextsController < ActivityPub::BaseController
def context_presenter
first_page = ActivityPub::CollectionPresenter.new(
id: items_context_url(@conversation, page_params),
type: :unordered,
part_of: context_url(@conversation),
part_of: items_context_url(@conversation),
next: next_page,
items: @items.map { |status| status.local? ? ActivityPub::TagManager.instance.uri_for(status) : status.uri }
)
@ -51,7 +52,7 @@ class ActivityPub::ContextsController < ActivityPub::BaseController
page = ActivityPub::CollectionPresenter.new(
id: items_context_url(@conversation, page_params),
type: :unordered,
part_of: context_url(@conversation),
part_of: items_context_url(@conversation),
next: next_page,
items: @items.map { |status| status.local? ? ActivityPub::TagManager.instance.uri_for(status) : status.uri }
)

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
class ActivityPub::FeatureAuthorizationsController < ActivityPub::BaseController
include Authorization
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_collection_item
def show
expires_in 30.seconds, public: true if public_fetch_mode?
render json: @collection_item, serializer: ActivityPub::FeatureAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
private
def pundit_user
signed_request_account
end
def set_collection_item
@collection_item = @account.collection_items.accepted.find(params[:id])
authorize @collection_item.collection, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end

View File

@ -1,77 +0,0 @@
# frozen_string_literal: true
class ActivityPub::FeaturedCollectionsController < ApplicationController
include SignatureAuthentication
include Authorization
include AccountOwnedConcern
PER_PAGE = 5
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
before_action :check_feature_enabled
before_action :require_account_signature!, if: -> { authorized_fetch_mode? }
before_action :set_collections
skip_around_action :set_locale
skip_before_action :require_functional!, unless: :limited_federation_mode?
def index
respond_to do |format|
format.json do
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
end
end
private
def set_collections
authorize @account, :index_collections?
@collections = @account.collections.page(params[:page]).per(PER_PAGE)
rescue Mastodon::NotPermittedError
not_found
end
def page_requested?
params[:page].present?
end
def next_page_url
ap_account_featured_collections_url(@account, page: @collections.next_page) if @collections.respond_to?(:next_page)
end
def prev_page_url
ap_account_featured_collections_url(@account, page: @collections.prev_page) if @collections.respond_to?(:prev_page)
end
def collection_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
id: ap_account_featured_collections_url(@account, page: params.fetch(:page, 1)),
type: :unordered,
size: @account.collections.count,
items: @collections,
part_of: ap_account_featured_collections_url(@account),
next: next_page_url,
prev: prev_page_url
)
else
ActivityPub::CollectionPresenter.new(
id: ap_account_featured_collections_url(@account),
type: :unordered,
size: @account.collections.count,
first: ap_account_featured_collections_url(@account, page: 1)
)
end
end
def check_feature_enabled
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
end
end

View File

@ -3,7 +3,6 @@
class ActivityPub::InboxesController < ActivityPub::BaseController
include JsonLdHelper
before_action :skip_large_payload
before_action :skip_unknown_actor_activity
before_action :require_actor_signature!
skip_before_action :authenticate_user!
@ -17,18 +16,14 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
private
def skip_large_payload
head 413 if request.content_length > ActivityPub::Activity::MAX_JSON_SIZE
end
def skip_unknown_actor_activity
head 202 if unknown_affected_account?
end
def unknown_affected_account?
json = JSON.parse(body)
json = Oj.load(body, mode: :strict)
json.is_a?(Hash) && %w(Delete Update).include?(json['type']) && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.exists?(uri: json['actor'])
rescue JSON::ParserError
rescue Oj::ParseError
false
end
@ -44,7 +39,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
return @body if defined?(@body)
@body = request.body.read
@body.presence&.force_encoding('UTF-8')
@body.force_encoding('UTF-8') if @body.present?
request.body.rewind if request.body.respond_to?(:rewind)

View File

@ -22,7 +22,7 @@ class ActivityPub::LikesController < ActivityPub::BaseController
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -24,7 +24,7 @@ class ActivityPub::QuoteAuthorizationsController < ActivityPub::BaseController
return not_found unless @quote.status.present? && @quote.quoted_status.present?
authorize @quote.quoted_status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -25,7 +25,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -22,7 +22,7 @@ class ActivityPub::SharesController < ActivityPub::BaseController
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -1,22 +0,0 @@
# frozen_string_literal: true
module Admin
class CollectionsController < BaseController
before_action :set_account
before_action :set_collection, only: :show
def show
authorize @collection, :show?
end
private
def set_account
@account = Account.find(params[:account_id])
end
def set_collection
@collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id])
end
end
end

View File

@ -5,15 +5,6 @@ module Admin
def index
authorize :custom_emoji, :index?
# If filtering by local emojis, remove by_domain filter.
params.delete(:by_domain) if params[:local].present?
# If filtering by domain, ensure remote filter is set.
if params[:by_domain].present?
params.delete(:local)
params[:remote] = '1'
end
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
@form = Form::CustomEmojiBatch.new
end

View File

@ -54,7 +54,7 @@ module Admin
end
# Allow transparently upgrading a domain block
if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain)
if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain.strip)
@domain_block = existing_domain_block
@domain_block.assign_attributes(resource_params)
end

View File

@ -5,8 +5,8 @@ class Admin::Fasp::Debug::CallbacksController < Admin::BaseController
authorize [:admin, :fasp, :provider], :update?
@callbacks = Fasp::DebugCallback
.includes(:fasp_provider)
.order(created_at: :desc)
.includes(:fasp_provider)
.order(created_at: :desc)
end
def destroy

View File

@ -34,11 +34,8 @@ class Admin::Instances::ModerationNotesController < Admin::BaseController
end
def set_instance
@instance = Instance.find_or_initialize_by(domain: normalized_domain)
end
def normalized_domain
TagManager.instance.normalize_domain(params[:instance_id])
domain = params[:instance_id]&.strip
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
end
def set_instance_note

View File

@ -55,11 +55,8 @@ module Admin
private
def set_instance
@instance = Instance.find_or_initialize_by(domain: normalized_domain)
end
def normalized_domain
TagManager.instance.normalize_domain(params[:id])
domain = params[:id]&.strip
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
end
def set_instances

View File

@ -13,7 +13,7 @@ class Admin::Reports::ActionsController < Admin::BaseController
case action_from_button
when 'delete', 'mark_as_sensitive'
Admin::ModerationAction.new(moderation_action_params).save!
Admin::StatusBatchAction.new(status_batch_action_params).save!
when 'silence', 'suspend'
Admin::AccountAction.new(account_action_params).save!
else
@ -25,8 +25,9 @@ class Admin::Reports::ActionsController < Admin::BaseController
private
def moderation_action_params
def status_batch_action_params
shared_params
.merge(status_ids: @report.status_ids)
end
def account_action_params

View File

@ -50,7 +50,7 @@ module Admin
private
def filtered_reports
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account, :collections)
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
end
def filter_params
@ -58,7 +58,7 @@ module Admin
end
def set_report
@report = Report.includes(collections: :accepted_collection_items).find(params[:id])
@report = Report.find(params[:id])
end
end
end

View File

@ -62,7 +62,7 @@ module Admin
def resource_params
params
.expect(user_role: [:name, :color, :highlighted, :position, :require_2fa, permissions_as_keys: []])
.expect(user_role: [:name, :color, :highlighted, :position, permissions_as_keys: []])
end
end
end

View File

@ -9,7 +9,7 @@ module Admin
@site_upload.destroy!
redirect_back_or_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
end
private

View File

@ -78,6 +78,8 @@ module Admin
'report'
elsif params[:remove_from_report]
'remove_from_report'
elsif params[:delete]
'delete'
end
end
end

View File

@ -47,7 +47,7 @@ class Api::Fasp::BaseController < ApplicationController
provider = nil
Linzer.verify!(request.rack_request, no_older_than: 5.minutes) do |keyid|
provider = Fasp::Provider.confirmed.find(keyid)
provider = Fasp::Provider.find(keyid)
Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
end

View File

@ -9,9 +9,9 @@ class Api::V1::Accounts::NotesController < Api::BaseController
def create
if params[:comment].blank?
current_account.account_notes.find_by(target_account: @account)&.destroy
AccountNote.find_by(account: current_account, target_account: @account)&.destroy
else
@note = current_account.account_notes.find_or_initialize_by(target_account: @account)
@note = AccountNote.find_or_initialize_by(account: current_account, target_account: @account)
@note.comment = params[:comment]
@note.save! if @note.changed?
end

View File

@ -38,7 +38,7 @@ class Api::V1::AccountsController < Api::BaseController
headers.merge!(response.headers)
self.response_body = response.body.to_json
self.response_body = Oj.dump(response.body)
self.status = response.status
rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e, 'account.username': :username, 'invite_request.text': :reason).as_json, status: 422

View File

@ -1,12 +1,10 @@
# frozen_string_literal: true
class Api::V1::AnnualReportsController < Api::BaseController
include AsyncRefreshesConcern
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:read, :generate]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:read, :generate]
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
before_action :require_user!
before_action :set_annual_report, only: [:show, :read]
before_action :set_annual_report, except: :index
def index
with_read_replica do
@ -30,28 +28,6 @@ class Api::V1::AnnualReportsController < Api::BaseController
relationships: @relationships
end
def state
render json: { state: report_state }
end
def generate
return render_empty unless year == AnnualReport.current_campaign
return render_empty if GeneratedAnnualReport.exists?(account_id: current_account.id, year: year)
async_refresh = AsyncRefresh.new(refresh_key)
if async_refresh.running?
add_async_refresh_header(async_refresh, retry_seconds: 2)
return head 202
end
add_async_refresh_header(AsyncRefresh.create(refresh_key), retry_seconds: 2)
GenerateAnnualReportWorker.perform_async(current_account.id, year)
head 202
end
def read
@annual_report.view!
render_empty
@ -59,21 +35,7 @@ class Api::V1::AnnualReportsController < Api::BaseController
private
def report_state
AnnualReport.new(current_account, year).state do |async_refresh|
add_async_refresh_header(async_refresh, retry_seconds: 2)
end
end
def refresh_key
"wrapstodon:#{current_account.id}:#{year}"
end
def year
params[:id]&.to_i
end
def set_annual_report
@annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: year)
@annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id])
end
end

View File

@ -18,14 +18,14 @@ class Api::V1::BlocksController < Api::BaseController
def paginated_blocks
@paginated_blocks ||= Block.eager_load(target_account: [:account_stat, :user])
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
def next_path

View File

@ -37,20 +37,20 @@ class Api::V1::ConversationsController < Api::BaseController
def paginated_conversations
AccountConversation.where(account: current_account)
.includes(
account: [:account_stat, user: :role],
last_status: [
:media_attachments,
:status_stat,
:tags,
{
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
active_mentions: :account,
account: [:account_stat, user: :role],
},
]
)
.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
.includes(
account: [:account_stat, user: :role],
last_status: [
:media_attachments,
:status_stat,
:tags,
{
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
active_mentions: :account,
account: [:account_stat, user: :role],
},
]
)
.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def next_path

View File

@ -1,86 +0,0 @@
# frozen_string_literal: true
class Api::V1::DonationCampaignsController < Api::BaseController
before_action :require_user!
STOPLIGHT_COOL_OFF_TIME = 60
STOPLIGHT_FAILURE_THRESHOLD = 10
def index
return head 204 if api_url.blank?
json = from_cache
return render json: json if json.present?
campaign = fetch_campaign
return head 204 if campaign.nil?
save_to_cache!(campaign)
render json: campaign
end
private
def api_url
Rails.configuration.x.donation_campaigns.api_url
end
def seed
@seed ||= Random.new(current_account.id).rand(100)
end
def from_cache
key = Rails.cache.read(request_key, raw: true)
return if key.blank?
campaign = Rails.cache.read("donation_campaign:#{key}", raw: true)
JSON.parse(campaign) if campaign.present?
end
def save_to_cache!(campaign)
return if campaign.blank?
Rails.cache.write_multi(
{
request_key => campaign_key(campaign),
"donation_campaign:#{campaign_key(campaign)}" => campaign.to_json,
},
expires_in: 1.hour,
raw: true
)
end
def fetch_campaign
stoplight_wrapper.run do
url = Addressable::URI.parse(api_url)
url.query_values = { platform: 'web', seed: seed, locale: locale, environment: Rails.configuration.x.donation_campaigns.environment }.compact
Request.new(:get, url.to_s).perform do |res|
return JSON.parse(res.body_with_limit) if res.code == 200
end
end
rescue *Mastodon::HTTP_CONNECTION_ERRORS, JSON::ParserError
nil
end
def stoplight_wrapper
Stoplight(
'donation_campaigns',
cool_off_time: STOPLIGHT_COOL_OFF_TIME,
threshold: STOPLIGHT_FAILURE_THRESHOLD
)
end
def request_key
"donation_campaign_request:#{seed}:#{locale}"
end
def campaign_key(campaign)
"#{campaign['id']}:#{campaign['locale']}"
end
def locale
I18n.locale.to_s
end
end

View File

@ -32,7 +32,13 @@ class Api::V1::MarkersController < Api::BaseController
private
def serialize_map(map)
map.transform_values { |value| ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer) }
serialized = {}
map.each_pair do |key, value|
serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json
end
Oj.dump(serialized)
end
def resource_params

View File

@ -18,14 +18,14 @@ class Api::V1::MutesController < Api::BaseController
def paginated_mutes
@paginated_mutes ||= Mute.eager_load(target_account: [:account_stat, :user])
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
def next_path

View File

@ -47,6 +47,10 @@ class Api::V1::Peers::SearchController < Api::BaseController
end
def normalized_domain
TagManager.instance.normalize_domain(params[:q])
TagManager.instance.normalize_domain(query_value)
end
def query_value
params[:q].strip
end
end

View File

@ -17,7 +17,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
def set_poll
@poll = Poll.find(params[:poll_id])
authorize @poll.status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
def set_poll
@poll = Poll.find(params[:id])
authorize @poll.status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -1,43 +0,0 @@
# frozen_string_literal: true
class Api::V1::ProfilesController < Api::BaseController
before_action -> { doorkeeper_authorize! :profile, :read, :'read:accounts' }, except: [:update]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update]
before_action :require_user!
def show
@account = current_account
render json: @account, serializer: REST::ProfileSerializer
end
def update
@account = current_account
UpdateAccountService.new.call(@account, account_params, raise_error: true)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
render json: @account, serializer: REST::ProfileSerializer
rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e).as_json, status: 422
end
def account_params
params.permit(
:display_name,
:note,
:avatar,
:avatar_description,
:header,
:header_description,
:locked,
:bot,
:discoverable,
:hide_collections,
:indexable,
:show_media,
:show_media_replies,
:show_featured,
attribution_domains: [],
fields_attributes: [:name, :value]
)
end
end

View File

@ -23,10 +23,6 @@ class Api::V1::ReportsController < Api::BaseController
end
def report_params
if Mastodon::Feature.collections_enabled?
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], collection_ids: [], rule_ids: [])
else
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
end
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
end
end

View File

@ -10,7 +10,7 @@ class Api::V1::Statuses::BaseController < Api::BaseController
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -23,7 +23,7 @@ class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
bookmark&.destroy!
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -25,7 +25,7 @@ class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseControlle
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 ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -26,20 +26,20 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::AddNoteSerializer,
serializer: ActivityPub::AddSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(json.to_json, current_account.id)
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
end
def distribute_remove_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::RemoveNoteSerializer,
serializer: ActivityPub::RemoveSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(json.to_json, current_account.id)
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
end
end

View File

@ -41,8 +41,8 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController
if current_account&.id != @status.account_id
domains = @statuses.filter_map(&:account_domain).uniq
account_ids = @statuses.map(&:account_id).uniq
current_account&.preload_relations!(account_ids, domains)
@statuses.reject! { |status| StatusFilter.new(status, current_account).filtered? }
relations = current_account&.relations_map(account_ids, domains) || {}
@statuses.reject! { |status| StatusFilter.new(status, current_account, relations).filtered? }
end
end

View File

@ -36,7 +36,7 @@ class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
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 ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
@ -45,7 +45,7 @@ class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
def set_reblog
@reblog = Status.find(params[:status_id])
authorize @reblog, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -66,7 +66,7 @@ class Api::V1::StatusesController < Api::BaseController
if async_refresh.running?
add_async_refresh_header(async_refresh)
elsif !current_account.nil? && @status.should_fetch_replies?
add_async_refresh_header(AsyncRefresh.create(refresh_key, count_results: true))
add_async_refresh_header(AsyncRefresh.create(refresh_key))
WorkerBatch.new.within do |batch|
batch.connect(refresh_key, threshold: 1.0)
@ -106,7 +106,9 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.where(account: current_account).find(params[:id])
authorize @status, :update?
update_options = {
UpdateStatusService.new.call(
@status,
current_account.id,
text: status_params[:status],
media_ids: status_params[:media_ids],
media_attributes: status_params[:media_attributes],
@ -114,11 +116,8 @@ class Api::V1::StatusesController < Api::BaseController
language: status_params[:language],
spoiler_text: status_params[:spoiler_text],
poll: status_params[:poll],
}
update_options[:quote_approval_policy] = quote_approval_policy if status_params[:quote_approval_policy].present?
UpdateStatusService.new.call(@status, current_account.id, update_options)
quote_approval_policy: quote_approval_policy
)
render json: @status, serializer: REST::StatusSerializer
end
@ -127,13 +126,10 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.where(account: current_account).find(params[:id])
authorize @status, :destroy?
# JSON is generated before `discard_with_reblogs` in order to have the proper URL
# for media attachments, as it would otherwise redirect to the media proxy
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
@status.discard_with_reblogs
StatusPin.find_by(status: @status)&.destroy
@status.account.statuses_count = @status.account.statuses_count - 1
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) })
@ -149,7 +145,7 @@ class Api::V1::StatusesController < Api::BaseController
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end

View File

@ -39,6 +39,6 @@ class Api::V1::TagsController < Api::BaseController
def set_or_create_tag
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: params[:id], display_name: params[:id])
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
end
end

View File

@ -1,62 +0,0 @@
# frozen_string_literal: true
class Api::V1Alpha::CollectionItemsController < Api::BaseController
include Authorization
before_action :check_feature_enabled
before_action -> { doorkeeper_authorize! :write, :'write:collections' }
before_action :require_user!
before_action :set_collection
before_action :set_account, only: [:create]
before_action :set_collection_item, only: [:destroy, :revoke]
after_action :verify_authorized
def create
authorize @collection, :update?
authorize @account, :feature?
@item = AddAccountToCollectionService.new.call(@collection, @account)
render json: @item, serializer: REST::CollectionItemSerializer, adapter: :json
end
def destroy
authorize @collection, :update?
DeleteCollectionItemService.new.call(@collection_item)
head 200
end
def revoke
authorize @collection_item, :revoke?
RevokeCollectionItemService.new.call(@collection_item)
head 200
end
private
def set_collection
@collection = Collection.find(params[:collection_id])
end
def set_account
return render(json: { error: '`account_id` parameter is missing' }, status: 422) if params[:account_id].blank?
@account = Account.find(params[:account_id])
end
def set_collection_item
@collection_item = @collection.collection_items.find(params[:id])
end
def check_feature_enabled
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
end
end

View File

@ -1,117 +0,0 @@
# frozen_string_literal: true
class Api::V1Alpha::CollectionsController < Api::BaseController
include Authorization
DEFAULT_COLLECTIONS_LIMIT = 40
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: ValidationErrorFormatter.new(e).as_json }, status: 422
end
before_action :check_feature_enabled
before_action -> { authorize_if_got_token! :read, :'read:collections' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :write, :'write:collections' }, only: [:create, :update, :destroy]
before_action :require_user!, only: [:create, :update, :destroy]
before_action :set_account, only: [:index]
before_action :set_collections, only: [:index]
before_action :set_collection, only: [:show, :update, :destroy]
after_action :insert_pagination_headers, only: [:index]
after_action :verify_authorized
def index
cache_if_unauthenticated!
authorize @account, :index_collections?
render json: @collections, each_serializer: REST::CollectionSerializer, adapter: :json
rescue Mastodon::NotPermittedError
render json: { collections: [] }
end
def show
cache_if_unauthenticated!
authorize @collection, :show?
render json: @collection, serializer: REST::CollectionWithAccountsSerializer
end
def create
authorize Collection, :create?
@collection = CreateCollectionService.new.call(collection_creation_params, current_user.account)
render json: @collection, serializer: REST::CollectionSerializer, adapter: :json
end
def update
authorize @collection, :update?
UpdateCollectionService.new.call(@collection, collection_update_params)
render json: @collection, serializer: REST::CollectionSerializer, adapter: :json
end
def destroy
authorize @collection, :destroy?
DeleteCollectionService.new.call(@collection)
head 200
end
private
def set_account
@account = Account.find(params[:account_id])
end
def set_collections
@collections = @account.collections
.with_tag
.order(created_at: :desc)
.offset(offset_param)
.limit(limit_param(DEFAULT_COLLECTIONS_LIMIT))
@collections = @collections.discoverable unless @account == current_account
end
def set_collection
@collection = Collection.find(params[:id])
end
def collection_creation_params
params.permit(:name, :description, :language, :sensitive, :discoverable, :tag_name, account_ids: [])
end
def collection_update_params
params.permit(:name, :description, :language, :sensitive, :discoverable, :tag_name)
end
def check_feature_enabled
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
end
def next_path
return unless records_continue?
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
end
def prev_path
return if offset_param.zero?
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
end
def records_continue?
((offset_param * limit_param(DEFAULT_COLLECTIONS_LIMIT)) + @collections.size) < @account.collections.size
end
def offset_param
params[:offset].to_i
end
end

View File

@ -30,7 +30,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -62,7 +62,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def set_push_subscription
@push_subscription = ::Web::PushSubscription.where(user_id: active_session.user_id).find(params[:id])
@push_subscription = ::Web::PushSubscription.find(params[:id])
end
def subscription_params

View File

@ -9,7 +9,6 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern
include SessionTrackingConcern
include CacheConcern
include ErrorResponses
include PreloadingConcern
include DomainControlHelper
include DatabaseHelper
@ -18,12 +17,28 @@ class ApplicationController < ActionController::Base
helper_method :current_account
helper_method :current_session
helper_method :current_theme
helper_method :single_user_mode?
helper_method :use_seamless_external_login?
helper_method :sso_account_settings
helper_method :limited_federation_mode?
helper_method :skip_csrf_meta_tags?
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from ActionController::RoutingError, ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_content
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
rescue_from Seahorse::Client::NetworkingError do |e|
Rails.logger.warn "Storage server error: #{e}"
service_unavailable
end
before_action :check_self_destruct!
before_action :store_referrer, except: :raise_not_found, if: :devise_controller?
@ -47,25 +62,19 @@ class ApplicationController < ActionController::Base
return if request.referer.blank?
redirect_uri = URI(request.referer)
return if redirect_uri.path.start_with?('/auth', '/settings/two_factor_authentication', '/settings/otp_authentication')
return if redirect_uri.path.start_with?('/auth')
stored_url = redirect_uri.to_s if redirect_uri.host == request.host && redirect_uri.port == request.port
store_location_for(:user, stored_url)
end
def mfa_setup_path(path_params = {})
settings_two_factor_authentication_methods_path(path_params)
end
def require_functional!
return if current_user.functional?
respond_to do |format|
format.any do
if current_user.missing_2fa?
redirect_to mfa_setup_path
elsif current_user.confirmed?
if current_user.confirmed?
redirect_to edit_user_registration_path
else
redirect_to auth_setup_path
@ -77,8 +86,6 @@ class ApplicationController < ActionController::Base
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
elsif current_user.missing_2fa?
render json: { error: 'Your account requires two-factor authentication' }, status: 403
elsif !current_user.functional?
render json: { error: 'Your login is currently disabled' }, status: 403
end
@ -104,6 +111,42 @@ class ApplicationController < ActionController::Base
ActiveModel::Type::Boolean.new.cast(params[key])
end
def forbidden
respond_with_error(403)
end
def not_found
respond_with_error(404)
end
def gone
respond_with_error(410)
end
def unprocessable_content
respond_with_error(422)
end
def not_acceptable
respond_with_error(406)
end
def bad_request
respond_with_error(400)
end
def internal_server_error
respond_with_error(500)
end
def service_unavailable
respond_with_error(503)
end
def too_many_requests
respond_with_error(429)
end
def single_user_mode?
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.without_internal.exists?
end
@ -128,6 +171,19 @@ class ApplicationController < ActionController::Base
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
end
def current_theme
return Setting.theme unless Themes.instance.names.include? current_user&.setting_theme
current_user.setting_theme
end
def respond_with_error(code)
respond_to do |format|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end
end
def check_self_destruct!
return unless self_destruct?

Some files were not shown because too many files have changed in this diff Show More