1
0
mirror of https://github.com/funamitech/mastodon synced 2024-11-23 14:46:52 +09:00
This commit is contained in:
Noa Himesaka 2024-06-14 18:31:42 +09:00
commit 28a07d2e78
619 changed files with 7930 additions and 4325 deletions

View File

@ -1,20 +1,15 @@
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby # For details, see https://github.com/devcontainers/images/tree/main/src/ruby
FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye FROM mcr.microsoft.com/devcontainers/ruby:1-3.3-bookworm
# Install Rails # Install node version from .nvmrc
# RUN gem install rails webdrivers WORKDIR /app
COPY .nvmrc .
RUN /bin/bash --login -i -c "nvm install"
ARG NODE_VERSION="20" # Install additional OS packages
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" RUN apt-get update && \
export DEBIAN_FRONTEND=noninteractive && \
apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libvips42 libpam-dev
# [Optional] Uncomment this section to install additional OS packages. # Move welcome message to where VS Code expects it
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ COPY .devcontainer/welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
&& apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev
# [Optional] Uncomment this line to install additional gems.
RUN gem install foreman
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

View File

@ -1,6 +1,6 @@
{ {
"name": "Mastodon on GitHub Codespaces", "name": "Mastodon on GitHub Codespaces",
"dockerComposeFile": "../docker-compose.yml", "dockerComposeFile": "../compose.yaml",
"service": "app", "service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
@ -23,6 +23,8 @@
} }
}, },
"remoteUser": "root",
"otherPortsAttributes": { "otherPortsAttributes": {
"onAutoForward": "silent" "onAutoForward": "silent"
}, },
@ -37,7 +39,7 @@
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/post-create.sh", "postCreateCommand": "bin/setup",
"waitFor": "postCreateCommand", "waitFor": "postCreateCommand",
"customizations": { "customizations": {

View File

@ -1,12 +1,11 @@
version: '3'
services: services:
app: app:
working_dir: /workspaces/mastodon/
build: build:
context: . context: ..
dockerfile: Dockerfile dockerfile: .devcontainer/Dockerfile
volumes: volumes:
- ../..:/workspaces:cached - ..:/workspaces/mastodon:cached
environment: environment:
RAILS_ENV: development RAILS_ENV: development
NODE_ENV: development NODE_ENV: development

View File

@ -1,6 +1,6 @@
{ {
"name": "Mastodon on local machine", "name": "Mastodon on local machine",
"dockerComposeFile": "docker-compose.yml", "dockerComposeFile": "compose.yaml",
"service": "app", "service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
@ -23,12 +23,14 @@
} }
}, },
"remoteUser": "root",
"otherPortsAttributes": { "otherPortsAttributes": {
"onAutoForward": "silent" "onAutoForward": "silent"
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/post-create.sh", "postCreateCommand": "bin/setup",
"waitFor": "postCreateCommand", "waitFor": "postCreateCommand",
"customizations": { "customizations": {

View File

@ -1,27 +0,0 @@
#!/bin/bash
set -e # Fail the whole script on first error
# Fetch Ruby gem dependencies
bundle config path 'vendor/bundle'
bundle config with 'development test'
bundle install
# Make Gemfile.lock pristine again
git checkout -- Gemfile.lock
# Fetch Javascript dependencies
corepack prepare
yarn install --immutable
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup
# [re]create, migrate, and seed the development database
RAILS_ENV=development ./bin/rails db:setup
# Precompile assets for development
RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test
RAILS_ENV=test ./bin/rails assets:precompile

View File

@ -1,8 +1,7 @@
👋 Welcome to "Mastodon" in GitHub Codespaces! 👋 Welcome to your Mastodon Dev Container!
🛠️ Your environment is fully setup with all the required software. 🛠️ Your environment is fully setup with all the required software.
🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1). 💥 Run `bin/dev` to start the application processes.
📝 Edit away, run your app as usual, and we'll automatically make it available for you to access.
🥼 Run `RAILS_ENV=test bin/rails assets:precompile && RAILS_ENV=test bin/rspec` to run the test suite.

View File

@ -4,7 +4,8 @@ NODE_ENV=production
LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature # Secret values required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR # Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION

View File

@ -14,7 +14,7 @@ runs:
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev ${{ inputs.additional-system-dependencies }} sudo apt-get install -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }}
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1

4
.github/codecov.yml vendored
View File

@ -3,9 +3,9 @@ coverage:
status: status:
project: project:
default: default:
# Github status check is not blocking # GitHub status check is not blocking
informational: true informational: true
patch: patch:
default: default:
# Github status check is not blocking # GitHub status check is not blocking
informational: true informational: true

View File

@ -2,6 +2,7 @@
$schema: 'https://docs.renovatebot.com/renovate-schema.json', $schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [ extends: [
'config:recommended', 'config:recommended',
'customManagers:dockerfileVersions',
':labels(dependencies)', ':labels(dependencies)',
':prConcurrentLimitNone', // Remove limit for open PRs at any time. ':prConcurrentLimitNone', // Remove limit for open PRs at any time.
':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour. ':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour.
@ -59,7 +60,7 @@
dependencyDashboardApproval: true, dependencyDashboardApproval: true,
}, },
{ {
// Update Github Actions and Docker images weekly // Update GitHub Actions and Docker images weekly
matchManagers: ['github-actions', 'dockerfile', 'docker-compose'], matchManagers: ['github-actions', 'dockerfile', 'docker-compose'],
extends: ['schedule:weekly'], extends: ['schedule:weekly'],
}, },

View File

@ -133,7 +133,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick libpam-dev additional-system-dependencies: ffmpeg libpam-dev
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'
@ -148,6 +148,93 @@ jobs:
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-libvips:
name: Libvips tests
runs-on: ubuntu-24.04
needs:
- build
services:
postgres:
image: postgres:14-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
env:
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
DISABLE_SIMPLECOV: ${{ 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: true
strategy:
fail-fast: false
matrix:
ruby-version:
- '3.1'
- '3.2'
- '.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 libpam-dev libyaml-dev
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bin/rspec --tag paperclip_processing
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@v4
with:
files: coverage/lcov/mastodon.lcov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-e2e: test-e2e:
name: End to End testing name: End to End testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -209,7 +296,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick additional-system-dependencies: ffmpeg
- name: Set up Javascript environment - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript
@ -329,7 +416,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick additional-system-dependencies: ffmpeg
- name: Set up Javascript environment - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript

View File

@ -1,19 +0,0 @@
.DS_Store
.git/
.gitignore
.bundle/
.cache/
config/deploy/*
coverage
docs/
.env
log/*.log
neo4j/
node_modules/
public/assets/
public/system/
spec/
tmp/
.vagrant/
vendor/bundle/

2
.nvmrc
View File

@ -1 +1 @@
20.13 20.14

View File

@ -211,6 +211,11 @@ Style/PercentLiteralDelimiters:
Style/RedundantBegin: Style/RedundantBegin:
Enabled: false Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantfetchblock
Style/RedundantFetchBlock:
Enabled: false
# Reason: Overridden to reduce implicit StandardError rescues # Reason: Overridden to reduce implicit StandardError rescues
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror # https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
Style/RescueStandardError: Style/RescueStandardError:

View File

@ -169,16 +169,6 @@ Style/RedundantConstantBase:
- 'config/environments/production.rb' - 'config/environments/production.rb'
- 'config/initializers/sidekiq.rb' - 'config/initializers/sidekiq.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeForConstants.
Style/RedundantFetchBlock:
Exclude:
- 'config/initializers/1_hosts.rb'
- 'config/initializers/chewy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/puma.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try! # AllowedMethods: present?, blank?, presence, try, try!
@ -186,12 +176,6 @@ Style/SafeNavigation:
Exclude: Exclude:
- 'app/models/concerns/account/finder_concern.rb' - 'app/models/concerns/account/finder_concern.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
Exclude:
- 'config/initializers/paperclip.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: WordRegex. # Configuration parameters: WordRegex.
# SupportedStyles: percent, brackets # SupportedStyles: percent, brackets

View File

@ -1 +1 @@
3.3.1 3.3.2

View File

@ -1,22 +0,0 @@
# frozen_string_literal: true
if ENV['CI']
require 'simplecov-lcov'
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
else
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
end
SimpleCov.start 'rails' do
enable_coverage :branch
add_filter 'lib/linter'
add_group 'Libraries', 'lib'
add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters'
add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators'
end

View File

@ -2,6 +2,61 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [4.2.9] - 2024-05-30
### Security
- Update dependencies
- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf))
- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh))
- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553))
### Added
- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592))
- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092))
### Removed
- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862))
- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
### Fixed
- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450))
- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403))
- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306))
- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125))
- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119))
- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084))
- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022))
- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838))
- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597))
- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530))
- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379))
- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363))
## [4.2.8] - 2024-02-23
### Added
- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355))
In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week.
When this happens, users with the permission to change server settings will receive an email notification.
This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`.
### Changed
- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280))
If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations.
Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again.
### Fixed
- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335))
- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358))
## [4.2.7] - 2024-02-16 ## [4.2.7] - 2024-02-16
### Fixed ### Fixed

View File

@ -1,5 +1,8 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
# This file is designed for production server deployment, not local development work
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
# Please see https://docs.docker.com/engine/reference/builder for information about # Please see https://docs.docker.com/engine/reference/builder for information about
# the extended buildx capabilities used in this file. # the extended buildx capabilities used in this file.
# Make sure multiarch TARGETPLATFORM is available for interpolation # Make sure multiarch TARGETPLATFORM is available for interpolation
@ -7,22 +10,24 @@
ARG TARGETPLATFORM=${TARGETPLATFORM} ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM}
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.1"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
ARG RUBY_VERSION="3.3.1" # renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.3.2"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
# renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="20" ARG NODE_MAJOR_VERSION="20"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm" ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim) # Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node
# Ruby image to use for base image based on combined variables (ex: 3.3.1-slim-bookworm) # Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
# Example: v4.2.0-nightly.2023.11.09+something # Example: v4.2.0-nightly.2023.11.09+something
# Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"] # Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
ARG MASTODON_VERSION_PRERELEASE="" ARG MASTODON_VERSION_PRERELEASE=""
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="something"] # Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-12345"]
ARG MASTODON_VERSION_METADATA="" ARG MASTODON_VERSION_METADATA=""
# Allow Ruby on Rails to serve static files # Allow Ruby on Rails to serve static files
@ -43,6 +48,8 @@ ENV \
# Apply Mastodon version information # Apply Mastodon version information
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \ MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \ MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
# Enable libvips
MASTODON_USE_LIBVIPS=true \
# Apply Mastodon static files and YJIT options # Apply Mastodon static files and YJIT options
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \ RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \ RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
@ -97,7 +104,7 @@ RUN \
curl \ curl \
ffmpeg \ ffmpeg \
file \ file \
imagemagick \ libvips42 \
libjemalloc2 \ libjemalloc2 \
patchelf \ patchelf \
procps \ procps \

11
Gemfile
View File

@ -23,6 +23,7 @@ gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 1.0', require: false gem 'fog-openstack', '~> 1.0', require: false
gem 'kt-paperclip', '~> 7.2' gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'ruby-vips', '~> 2.2', require: false
gem 'active_model_serializers', '~> 0.10' gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8' gem 'addressable', '~> 2.8'
@ -56,7 +57,7 @@ gem 'hiredis', '~> 0.6'
gem 'htmlentities', '~> 4.3' gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.2.0' gem 'http', '~> 5.2.0'
gem 'http_accept_language', '~> 2.1' gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.6.2' gem 'httplog', '~> 1.7.0'
gem 'i18n' gem 'i18n'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'inline_svg' gem 'inline_svg'
@ -103,8 +104,10 @@ gem 'rdf-normalize', '~> 0.5'
gem 'private_address_check', '~> 0.5' gem 'private_address_check', '~> 0.5'
gem 'opentelemetry-api', '~> 1.2.5'
group :opentelemetry do group :opentelemetry do
gem 'opentelemetry-exporter-otlp', '~> 0.26.3', require: false gem 'opentelemetry-exporter-otlp', '~> 0.27.0', require: false
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
@ -132,7 +135,7 @@ group :test do
gem 'email_spec' gem 'email_spec'
# Extra RSpec extension methods and helpers for sidekiq # Extra RSpec extension methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0' gem 'rspec-sidekiq', '~> 5.0'
# Browser integration testing # Browser integration testing
gem 'capybara', '~> 3.39' gem 'capybara', '~> 3.39'
@ -178,7 +181,7 @@ group :development do
# Preview mail in the browser # Preview mail in the browser
gem 'letter_opener', '~> 1.8' gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0' gem 'letter_opener_web', '~> 3.0'
# Security analysis CLI tools # Security analysis CLI tools
gem 'brakeman', '~> 6.0', require: false gem 'brakeman', '~> 6.0', require: false

View File

@ -10,35 +10,35 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.2) actioncable (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.2) actionmailbox (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
activejob (= 7.1.3.2) activejob (= 7.1.3.4)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3.2) actionmailer (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
actionview (= 7.1.3.2) actionview (= 7.1.3.4)
activejob (= 7.1.3.2) activejob (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.2) actionpack (7.1.3.4)
actionview (= 7.1.3.2) actionview (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2) actiontext (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.2) actionview (7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2) activejob (7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.2) activemodel (7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
activerecord (7.1.3.2) activerecord (7.1.3.4)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.2) activestorage (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
activejob (= 7.1.3.2) activejob (= 7.1.3.4)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.2) activesupport (7.1.3.4)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -100,17 +100,17 @@ GEM
attr_required (1.0.2) attr_required (1.0.2)
awrence (1.2.1) awrence (1.2.1)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.922.0) aws-partitions (1.940.0)
aws-sdk-core (3.194.1) aws-sdk-core (3.197.0)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.80.0) aws-sdk-kms (1.83.0)
aws-sdk-core (~> 3, >= 3.193.0) aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.149.1) aws-sdk-s3 (1.152.0)
aws-sdk-core (~> 3, >= 3.194.0) aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0) aws-sigv4 (1.8.0)
@ -168,7 +168,7 @@ GEM
climate_control (1.2.0) climate_control (1.2.0)
cocoon (1.2.15) cocoon (1.2.15)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.2.3) concurrent-ruby (1.3.3)
connection_pool (2.4.1) connection_pool (2.4.1)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
@ -231,7 +231,7 @@ GEM
tzinfo tzinfo
excon (0.110.0) excon (0.110.0)
fabrication (2.31.0) fabrication (2.31.0)
faker (3.3.1) faker (3.4.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (1.10.3) faraday (1.10.3)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
@ -272,7 +272,7 @@ GEM
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
fog-openstack (1.1.0) fog-openstack (1.1.1)
fog-core (~> 2.1) fog-core (~> 2.1)
fog-json (>= 1.0) fog-json (>= 1.0)
formatador (1.1.0) formatador (1.1.0)
@ -321,7 +321,7 @@ GEM
http-form_data (2.3.0) http-form_data (2.3.0)
http_accept_language (2.1.1) http_accept_language (2.1.1)
httpclient (2.8.3) httpclient (2.8.3)
httplog (1.6.3) httplog (1.7.0)
rack (>= 2.0) rack (>= 2.0)
rainbow (>= 2.0.0) rainbow (>= 2.0.0)
i18n (1.14.5) i18n (1.14.5)
@ -389,10 +389,10 @@ GEM
addressable (~> 2.8) addressable (~> 2.8)
letter_opener (1.10.0) letter_opener (1.10.0)
launchy (>= 2.2, < 4) launchy (>= 2.2, < 4)
letter_opener_web (2.0.0) letter_opener_web (3.0.0)
actionmailer (>= 5.2) actionmailer (>= 6.1)
letter_opener (~> 1.7) letter_opener (~> 1.9)
railties (>= 5.2) railties (>= 6.1)
rexml rexml
link_header (0.0.8) link_header (0.0.8)
llhttp-ffi (0.5.0) llhttp-ffi (0.5.0)
@ -422,10 +422,10 @@ GEM
memory_profiler (1.0.1) memory_profiler (1.0.1)
mime-types (3.5.2) mime-types (3.5.2)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2024.0305) mime-types-data (3.2024.0507)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.6) mini_portile2 (2.8.7)
minitest (5.22.3) minitest (5.23.1)
msgpack (1.7.2) msgpack (1.7.2)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.4.0) multipart-post (2.4.0)
@ -434,7 +434,7 @@ GEM
uri uri
net-http-persistent (4.0.2) net-http-persistent (4.0.2)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.4.10) net-imap (0.4.12)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@ -444,8 +444,8 @@ GEM
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.1) nio4r (2.7.3)
nokogiri (1.16.4) nokogiri (1.16.5)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nsa (0.3.0) nsa (0.3.0)
@ -453,7 +453,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.16.3) oj (3.16.4)
bigdecimal (>= 3.0) bigdecimal (>= 3.0)
omniauth (2.1.2) omniauth (2.1.2)
hashie (>= 3.4.6) hashie (>= 3.4.6)
@ -489,7 +489,7 @@ GEM
opentelemetry-api (1.2.5) opentelemetry-api (1.2.5)
opentelemetry-common (0.20.1) opentelemetry-common (0.20.1)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-exporter-otlp (0.26.3) opentelemetry-exporter-otlp (0.27.0)
google-protobuf (~> 3.14) google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.3) googleapis-common-protos-types (~> 1.3)
opentelemetry-api (~> 1.1) opentelemetry-api (~> 1.1)
@ -578,15 +578,15 @@ GEM
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.14.18) ox (2.14.18)
parallel (1.24.0) parallel (1.25.1)
parser (3.3.1.0) parser (3.3.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
parslet (2.0.0) parslet (2.0.0)
pastel (0.8.0) pastel (0.8.0)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.5.6) pg (1.5.6)
pghero (3.4.1) pghero (3.5.0)
activerecord (>= 6) activerecord (>= 6)
premailer (1.23.0) premailer (1.23.0)
addressable addressable
@ -597,7 +597,7 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
propshaft (0.8.0) propshaft (0.9.0)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
@ -610,7 +610,7 @@ GEM
pundit (2.3.2) pundit (2.3.2)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.7.3) racc (1.8.0)
rack (2.2.9) rack (2.2.9)
rack-attack (6.7.0) rack-attack (6.7.0)
rack (>= 1.0, < 4) rack (>= 1.0, < 4)
@ -634,20 +634,20 @@ GEM
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.3.2) rails (7.1.3.4)
actioncable (= 7.1.3.2) actioncable (= 7.1.3.4)
actionmailbox (= 7.1.3.2) actionmailbox (= 7.1.3.4)
actionmailer (= 7.1.3.2) actionmailer (= 7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
actiontext (= 7.1.3.2) actiontext (= 7.1.3.4)
actionview (= 7.1.3.2) actionview (= 7.1.3.4)
activejob (= 7.1.3.2) activejob (= 7.1.3.4)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.4)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.2) railties (= 7.1.3.4)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9) rails-i18n (7.0.9)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.3.2) railties (7.1.3.4)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.4)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.4)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -685,15 +685,16 @@ GEM
redis (>= 4) redis (>= 4)
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0) regexp_parser (2.9.2)
reline (0.5.6) reline (0.5.8)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.6.0) request_store (1.6.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.2.6) rexml (3.2.8)
strscan (>= 3.0.9)
rotp (6.3.0) rotp (6.3.0)
rouge (4.2.1) rouge (4.2.1)
rpam2 (4.0.2) rpam2 (4.0.2)
@ -708,7 +709,7 @@ GEM
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-github (2.4.0) rspec-github (2.4.0)
rspec-core (~> 3.0) rspec-core (~> 3.0)
rspec-mocks (3.13.0) rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (6.1.2) rspec-rails (6.1.2)
@ -719,13 +720,13 @@ GEM
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-sidekiq (4.2.0) rspec-sidekiq (5.0.0)
rspec-core (~> 3.0) rspec-core (~> 3.0)
rspec-expectations (~> 3.0) rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8) sidekiq (>= 5, < 8)
rspec-support (3.13.1) rspec-support (3.13.1)
rubocop (1.63.5) rubocop (1.64.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -738,19 +739,19 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3) rubocop-ast (1.31.3)
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-capybara (2.20.0) rubocop-capybara (2.21.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.25.1) rubocop-factory_bot (2.25.1)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-performance (1.21.0) rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1) rubocop-rails (2.25.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (2.29.2) rubocop-rspec (2.31.0)
rubocop (~> 1.40) rubocop (~> 1.40)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22) rubocop-factory_bot (~> 2.22)
@ -762,6 +763,8 @@ GEM
ruby-saml (1.16.0) ruby-saml (1.16.0)
nokogiri (>= 1.13.10) nokogiri (>= 1.13.10)
rexml rexml
ruby-vips (2.2.1)
ffi (~> 1.12)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
rufus-scheduler (3.9.1) rufus-scheduler (3.9.1)
@ -774,7 +777,7 @@ GEM
scenic (1.8.0) scenic (1.8.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
selenium-webdriver (4.20.1) selenium-webdriver (4.21.1)
base64 (~> 0.2) base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@ -798,7 +801,7 @@ GEM
thor (>= 0.20, < 3.0) thor (>= 0.20, < 3.0)
simple-navigation (4.4.0) simple-navigation (4.4.0)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
simple_form (5.3.0) simple_form (5.3.1)
actionpack (>= 5.2) actionpack (>= 5.2)
activemodel (>= 5.2) activemodel (>= 5.2)
simplecov (0.22.0) simplecov (0.22.0)
@ -815,6 +818,7 @@ GEM
stringio (3.1.0) stringio (3.1.0)
strong_migrations (1.8.0) strong_migrations (1.8.0)
activerecord (>= 5.2) activerecord (>= 5.2)
strscan (3.1.0)
swd (1.3.0) swd (1.3.0)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
@ -875,7 +879,7 @@ GEM
webfinger (1.2.0) webfinger (1.2.0)
activesupport activesupport
httpclient (>= 2.4) httpclient (>= 2.4)
webmock (3.23.0) webmock (3.23.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@ -893,7 +897,7 @@ GEM
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.13) zeitwerk (2.6.15)
PLATFORMS PLATFORMS
ruby ruby
@ -943,7 +947,7 @@ DEPENDENCIES
htmlentities (~> 4.3) htmlentities (~> 4.3)
http (~> 5.2.0) http (~> 5.2.0)
http_accept_language (~> 2.1) http_accept_language (~> 2.1)
httplog (~> 1.6.2) httplog (~> 1.7.0)
i18n i18n
i18n-tasks (~> 1.0) i18n-tasks (~> 1.0)
idn-ruby idn-ruby
@ -955,7 +959,7 @@ DEPENDENCIES
kaminari (~> 1.2) kaminari (~> 1.2)
kt-paperclip (~> 7.2) kt-paperclip (~> 7.2)
letter_opener (~> 1.8) letter_opener (~> 1.8)
letter_opener_web (~> 2.0) letter_opener_web (~> 3.0)
link_header (~> 0.0) link_header (~> 0.0)
lograge (~> 0.12) lograge (~> 0.12)
mail (~> 2.8) mail (~> 2.8)
@ -973,7 +977,8 @@ DEPENDENCIES
omniauth-rails_csrf_protection (~> 1.0) omniauth-rails_csrf_protection (~> 1.0)
omniauth-saml (~> 2.0) omniauth-saml (~> 2.0)
omniauth_openid_connect (~> 0.6.1) omniauth_openid_connect (~> 0.6.1)
opentelemetry-exporter-otlp (~> 0.26.3) opentelemetry-api (~> 1.2.5)
opentelemetry-exporter-otlp (~> 0.27.0)
opentelemetry-instrumentation-active_job (~> 0.7.1) opentelemetry-instrumentation-active_job (~> 0.7.1)
opentelemetry-instrumentation-active_model_serializers (~> 0.20.1) opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
@ -1012,7 +1017,7 @@ DEPENDENCIES
rqrcode (~> 2.2) rqrcode (~> 2.2)
rspec-github (~> 2.4) rspec-github (~> 2.4)
rspec-rails (~> 6.0) rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0) rspec-sidekiq (~> 5.0)
rubocop rubocop
rubocop-capybara rubocop-capybara
rubocop-performance rubocop-performance
@ -1020,6 +1025,7 @@ DEPENDENCIES
rubocop-rspec rubocop-rspec
ruby-prof ruby-prof
ruby-progressbar (~> 1.13) ruby-progressbar (~> 1.13)
ruby-vips (~> 2.2)
rubyzip (~> 2.3) rubyzip (~> 2.3)
sanitize (~> 6.0) sanitize (~> 6.0)
scenic (~> 1.7) scenic (~> 1.7)
@ -1047,7 +1053,7 @@ DEPENDENCIES
xorcist (~> 1.1) xorcist (~> 1.1)
RUBY VERSION RUBY VERSION
ruby 3.3.1p55 ruby 3.3.2p78
BUNDLED WITH BUNDLED WITH
2.5.9 2.5.11

View File

@ -100,7 +100,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
- **Ruby** 3.1+ - **Ruby** 3.1+
- **Node.js** 18+ - **Node.js** 18+
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
## Development ## Development
@ -114,40 +114,51 @@ A **Vagrant** configuration is included for development purposes. To use it, com
- Run `vagrant ssh -c "cd /vagrant && bin/dev"` - Run `vagrant ssh -c "cd /vagrant && bin/dev"`
- Open `http://mastodon.local` in your browser - Open `http://mastodon.local` in your browser
### MacOS ### macOS
To set up **MacOS** for native development, complete the following steps: To set up **macOS** for native development, complete the following steps:
- Use a Ruby version manager to install the specified version from `.ruby-version` - Install [Homebrew] and run `brew install postgresql@14 redis imagemagick
- Run `bundle` to install required gems libidn nvm` to install the required project dependencies
- Run `brew install postgresql@14 redis imagemagick libidn` to install required dependencies - Use a Ruby version manager to activate the ruby in `.ruby-version` and run
- Navigate to Mastodon's root directory and run `brew install nvm` then `nvm use` to use the version from `.nvmrc` `nvm use` to activate the node version from `.nvmrc`
- Run `yarn` to install required packages - Run the `bin/setup` script, which will install the required ruby gems and node
- Run `corepack enable && corepack prepare` packages and prepare the database for local development
- Run `RAILS_ENV=development bundle exec rails db:setup` - Finally, run the `bin/dev` script which will launch services via `overmind`
- Finally, run `bin/dev` which will launch the local services via `overmind` (if installed) or `foreman` (if installed) or `foreman`
### Docker ### Docker
For development with **Docker**, complete the following steps: For production hosting and deployment with **Docker**, use the `Dockerfile` and
`docker-compose.yml` in the project root directory.
- Install Docker Desktop For local development, install and launch [Docker], and run:
- Run `docker compose -f .devcontainer/docker-compose.yml up -d`
- Run `docker compose -f .devcontainer/docker-compose.yml exec app .devcontainer/post-create.sh`
- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev`
If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers). ```shell
docker compose -f .devcontainer/compose.yaml up -d
docker compose -f .devcontainer/compose.yaml exec app bin/setup
docker compose -f .devcontainer/compose.yaml exec app bin/dev
```
### Dev Containers
Within IDEs that support the [Development Containers] specification, start the
"Mastodon on local machine" container from the editor. The necessary `docker
compose` commands to build and setup the container should run automatically. For
**Visual Studio Code** this requires installing the [Dev Container extension].
### GitHub Codespaces ### GitHub Codespaces
To get you coding in just a few minutes, GitHub Codespaces provides a web-based version of Visual Studio Code and a cloud-hosted development environment fully configured with the software needed for this project.. [GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted
development environment configured with the software needed for this project.
- Click this button to create a new codespace:<br> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)][codespace]
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json)
- Wait for the environment to build. This will take a few minutes. - Click the button to create a new codespace, and confirm the options
- When the editor is ready, run `bin/dev` in the terminal. - Wait for the environment to build (takes a few minutes)
- After a few seconds, a popup will appear with a button labeled _Open in Browser_. This will open Mastodon. - When the editor is ready, run `bin/dev` in the terminal
- On the _Ports_ tab, right click on the “stream” row and select _Port visibility__Public_. - Wait for an _Open in Browser_ prompt. This will open Mastodon
- On the _Ports_ tab "stream" setting change _Port visibility_ → _Public_
## Contributing ## Contributing
@ -166,4 +177,10 @@ This program is free software: you can redistribute it and/or modify it under th
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
>>>>>>> 3341db939cd077820ad598b0445d02ab2382eaf4
[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json
[Dev Container extension]: https://containers.dev/supporting#dev-containers
[Development Containers]: https://containers.dev/supporting
[Docker]: https://docs.docker.com
[GitHub Codespaces]: https://docs.github.com/en/codespaces
[Homebrew]: https://brew.sh

View File

@ -2,7 +2,7 @@
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either: If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either:
- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new) - open a [GitHub security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
- reach us at <security@joinmastodon.org> - reach us at <security@joinmastodon.org>
You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.

6
Vagrantfile vendored
View File

@ -151,6 +151,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
vb.customize ["modifyvm", :id, "--nictype2", "virtio"] vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end end
config.vm.provider :libvirt do |libvirt|
libvirt.cpus = 3
libvirt.memory = 8192
end
# This uses the vagrant-hostsupdater plugin, and lets you # This uses the vagrant-hostsupdater plugin, and lets you
# access the development site at http://mastodon.local. # access the development site at http://mastodon.local.
# If you change it, also change it in .env.vagrant before provisioning # If you change it, also change it in .env.vagrant before provisioning

View File

@ -25,7 +25,7 @@ class AccountsController < ApplicationController
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
@statuses = filtered_statuses.without_reblogs.limit(limit) @statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status) @statuses = preload_collection(@statuses, Status)
end end
format.json do format.json do

View File

@ -18,7 +18,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items def set_items
case params[:id] case params[:id]
when 'featured' when 'featured'
@items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) } @items = for_signed_account { preload_collection(@account.pinned_statuses.not_local_only, Status) }
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) } @items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
when 'tags' when 'tags'
@items = for_signed_account { @account.featured_tags } @items = for_signed_account { @account.featured_tags }

View File

@ -60,7 +60,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses def set_statuses
return unless page_requested? return unless page_requested?
@statuses = cache_collection_paginated_by_id( @statuses = preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, signed_request_account).results, AccountStatusesFilter.new(@account, signed_request_account).results,
Status, Status,
LIMIT, LIMIT,

View File

@ -128,7 +128,7 @@ module Admin
def unblock_email def unblock_email
authorize @account, :unblock_email? authorize @account, :unblock_email?
CanonicalEmailBlock.matching_account(@account).delete_all CanonicalEmailBlock.where(reference_account: @account).delete_all
log_action :unblock_email, @account log_action :unblock_email, @account

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Accounts::CredentialsController < Api::BaseController class Api::V1::Accounts::CredentialsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts', :'read:me' }, except: [:update] before_action -> { doorkeeper_authorize! :profile, :read, :'read:accounts' }, except: [:update]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update]
before_action :require_user! before_action :require_user!

View File

@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -19,11 +19,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def load_statuses def load_statuses
@account.unavailable? ? [] : cached_account_statuses @account.unavailable? ? [] : preloaded_account_statuses
end end
def cached_account_statuses def preloaded_account_statuses
cache_collection_paginated_by_id( preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, current_account, params).results, AccountStatusesFilter.new(@account, current_account, params).results,
Status, Status,
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),

View File

@ -106,11 +106,11 @@ class Api::V1::AccountsController < Api::BaseController
end end
def account_ids def account_ids
Array(accounts_params[:ids]).uniq.map(&:to_i) Array(accounts_params[:id]).uniq.map(&:to_i)
end end
def accounts_params def accounts_params
params.permit(ids: []) params.permit(id: [])
end end
def account_params def account_params

View File

@ -16,8 +16,6 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :canonical_email_block, :index? authorize :canonical_email_block, :index?
render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
@ -80,8 +78,4 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
def records_continue? def records_continue?
@canonical_email_blocks.size == limit_param(LIMIT) @canonical_email_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -14,8 +14,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :domain_allow, :index? authorize :domain_allow, :index?
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
@ -77,10 +75,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
@domain_allows.size == limit_param(LIMIT) @domain_allows.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
def resource_params def resource_params
params.permit(:domain) params.permit(:domain)
end end

View File

@ -14,8 +14,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :domain_block, :index? authorize :domain_block, :index?
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
@ -93,10 +91,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
@domain_blocks.size == limit_param(LIMIT) @domain_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
def resource_params def resource_params
params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
end end

View File

@ -14,10 +14,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(
limit
).freeze
def index def index
authorize :email_domain_block, :index? authorize :email_domain_block, :index?
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
@ -73,8 +69,4 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
def records_continue? def records_continue?
@email_domain_blocks.size == limit_param(LIMIT) @email_domain_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -14,10 +14,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(
limit
).freeze
def index def index
authorize :ip_block, :index? authorize :ip_block, :index?
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
@ -78,8 +74,4 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
def records_continue? def records_continue?
@ip_blocks.size == limit_param(LIMIT) @ip_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -12,7 +12,6 @@ class Api::V1::Admin::TagsController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
LIMIT = 100 LIMIT = 100
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :tag, :index? authorize :tag, :index?
@ -59,8 +58,4 @@ class Api::V1::Admin::TagsController < Api::BaseController
def records_continue? def records_continue?
@tags.size == limit_param(LIMIT) @tags.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -12,8 +12,6 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :preview_card_provider, :index? authorize :preview_card_provider, :index?
@ -57,8 +55,4 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
def records_continue? def records_continue?
@providers.size == limit_param(LIMIT) @providers.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show def show
return doorkeeper_render_error unless valid_doorkeeper_token? return doorkeeper_render_error unless valid_doorkeeper_token?
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes) render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
end end
end end

View File

@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create def create
@app = Doorkeeper::Application.create!(application_options) @app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer render json: @app, serializer: REST::CredentialApplicationSerializer
end end
private private
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
end end
def app_params def app_params
params.permit(:client_name, :redirect_uris, :scopes, :website) params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
end end
end end

View File

@ -43,8 +43,4 @@ class Api::V1::BlocksController < Api::BaseController
def records_continue? def records_continue?
paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -13,11 +13,11 @@ class Api::V1::BookmarksController < Api::BaseController
private private
def load_statuses def load_statuses
cached_bookmarks preloaded_bookmarks
end end
def cached_bookmarks def preloaded_bookmarks
cache_collection(results.map(&:status), Status) preload_collection(results.map(&:status), Status)
end end
def results def results
@ -46,8 +46,4 @@ class Api::V1::BookmarksController < Api::BaseController
def records_continue? def records_continue?
results.size == limit_param(DEFAULT_STATUSES_LIMIT) results.size == limit_param(DEFAULT_STATUSES_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -38,15 +38,15 @@ class Api::V1::ConversationsController < Api::BaseController
def paginated_conversations def paginated_conversations
AccountConversation.where(account: current_account) AccountConversation.where(account: current_account)
.includes( .includes(
account: :account_stat, account: [:account_stat, user: :role],
last_status: [ last_status: [
:media_attachments, :media_attachments,
:status_stat, :status_stat,
:tags, :tags,
{ {
preview_cards_status: :preview_card, preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
active_mentions: [account: :account_stat], active_mentions: :account,
account: :account_stat, account: [:account_stat, user: :role],
}, },
] ]
) )
@ -72,8 +72,4 @@ class Api::V1::ConversationsController < Api::BaseController
def records_continue? def records_continue?
@conversations.size == limit_param(LIMIT) @conversations.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -44,8 +44,4 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
def records_continue? def records_continue?
@encrypted_messages.size == limit_param(LIMIT) @encrypted_messages.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -54,10 +54,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
@blocks.size == limit_param(BLOCK_LIMIT) @blocks.size == limit_param(BLOCK_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def domain_block_params def domain_block_params
params.permit(:domain) params.permit(:domain)
end end

View File

@ -48,10 +48,6 @@ class Api::V1::EndorsementsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def unlimited? def unlimited?
params[:limit] == '0' params[:limit] == '0'
end end

View File

@ -13,11 +13,11 @@ class Api::V1::FavouritesController < Api::BaseController
private private
def load_statuses def load_statuses
cached_favourites preloaded_favourites
end end
def cached_favourites def preloaded_favourites
cache_collection(results.map(&:status), Status) preload_collection(results.map(&:status), Status)
end end
def results def results
@ -46,8 +46,4 @@ class Api::V1::FavouritesController < Api::BaseController
def records_continue? def records_continue?
results.size == limit_param(DEFAULT_STATUSES_LIMIT) results.size == limit_param(DEFAULT_STATUSES_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -67,8 +67,4 @@ class Api::V1::FollowRequestsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -37,8 +37,4 @@ class Api::V1::FollowedTagsController < Api::BaseController
def records_continue? def records_continue?
@results.size == limit_param(TAGS_LIMIT) @results.size == limit_param(TAGS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::B
before_action :set_extended_description before_action :set_extended_description
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
skip_around_action :set_locale skip_around_action :set_locale
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
before_action :set_rules before_action :set_rules
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -6,7 +6,7 @@ class Api::V1::InstancesController < Api::BaseController
vary_by '' vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -75,10 +75,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def unlimited? def unlimited?
params[:limit] == '0' params[:limit] == '0'
end end

View File

@ -43,8 +43,4 @@ class Api::V1::MutesController < Api::BaseController
def records_continue? def records_continue?
paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -41,7 +41,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
) )
NotificationRequest.preload_cache_collection(requests) do |statuses| NotificationRequest.preload_cache_collection(requests) do |statuses|
cache_collection(statuses, Status) preload_collection(statuses, Status)
end end
end end

View File

@ -50,7 +50,7 @@ class Api::V1::NotificationsController < Api::BaseController
) )
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
cache_collection(target_statuses, Status) preload_collection(target_statuses, Status)
end end
end end

View File

@ -43,10 +43,6 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
params.permit(:scheduled_at) params.permit(:scheduled_at)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue?
end end

View File

@ -53,8 +53,4 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -49,8 +49,4 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -26,13 +26,13 @@ class Api::V1::StatusesController < Api::BaseController
DESCENDANTS_DEPTH_LIMIT = 20 DESCENDANTS_DEPTH_LIMIT = 20
def index def index
@statuses = cache_collection(@statuses, Status) @statuses = preload_collection(@statuses, Status)
render json: @statuses, each_serializer: REST::StatusSerializer render json: @statuses, each_serializer: REST::StatusSerializer
end end
def show def show
cache_if_unauthenticated! cache_if_unauthenticated!
@status = cache_collection([@status], Status).first @status = preload_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
@ -51,8 +51,8 @@ class Api::V1::StatusesController < Api::BaseController
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account) ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit) descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
loaded_ancestors = cache_collection(ancestors_results, Status) loaded_ancestors = preload_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status) loaded_descendants = preload_collection(descendants_results, Status)
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants statuses = [@status] + @context.ancestors + @context.descendants
@ -143,11 +143,11 @@ class Api::V1::StatusesController < Api::BaseController
end end
def status_ids def status_ids
Array(statuses_params[:ids]).uniq.map(&:to_i) Array(statuses_params[:id]).uniq.map(&:to_i)
end end
def statuses_params def statuses_params
params.permit(ids: []) params.permit(id: [])
end end
def status_params def status_params
@ -191,8 +191,4 @@ class Api::V1::StatusesController < Api::BaseController
def serialized_accounts(accounts) def serialized_accounts(accounts)
ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer) ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -15,11 +15,11 @@ class Api::V1::Timelines::DirectController < Api::BaseController
private private
def load_statuses def load_statuses
cached_direct_statuses preloaded_direct_statuses
end end
def cached_direct_statuses def preloaded_direct_statuses
cache_collection direct_statuses, Status preload_collection direct_statuses, Status
end end
def direct_statuses def direct_statuses

View File

@ -21,11 +21,11 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
private private
def load_statuses def load_statuses
cached_home_statuses preloaded_home_statuses
end end
def cached_home_statuses def preloaded_home_statuses
cache_collection home_statuses, Status preload_collection home_statuses, Status
end end
def home_statuses def home_statuses

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
before_action :set_preview_card
before_action :set_statuses
PERMITTED_PARAMS = %i(
url
limit
).freeze
def show
cache_if_unauthenticated!
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def require_auth?
!Setting.timeline_preview
end
def set_preview_card
@preview_card = PreviewCard.joins(:trend).merge(PreviewCardTrend.allowed).find_by!(url: params[:url])
end
def set_statuses
@statuses = @preview_card.nil? ? [] : preload_collection(link_timeline_statuses, Status)
end
def link_timeline_statuses
link_feed.get(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id],
params[:min_id]
)
end
def link_feed
LinkFeed.new(@preview_card, current_account)
end
def next_path
api_v1_timelines_link_url next_path_params
end
def prev_path
api_v1_timelines_link_url prev_path_params
end
end

View File

@ -21,11 +21,11 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
end end
def set_statuses def set_statuses
@statuses = cached_list_statuses @statuses = preloaded_list_statuses
end end
def cached_list_statuses def preloaded_list_statuses
cache_collection list_statuses, Status preload_collection list_statuses, Status
end end
def list_statuses def list_statuses

View File

@ -18,11 +18,11 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
end end
def load_statuses def load_statuses
cached_public_statuses_page preloaded_public_statuses_page
end end
def cached_public_statuses_page def preloaded_public_statuses_page
cache_collection(public_statuses, Status) preload_collection(public_statuses, Status)
end end
def public_statuses def public_statuses

View File

@ -23,11 +23,11 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
end end
def load_statuses def load_statuses
cached_tagged_statuses preloaded_tagged_statuses
end end
def cached_tagged_statuses def preloaded_tagged_statuses
@tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status) @tag.nil? ? [] : preload_collection(tag_timeline_statuses, Status)
end end
def tag_timeline_statuses def tag_timeline_statuses

View File

@ -34,10 +34,6 @@ class Api::V1::Trends::LinksController < Api::BaseController
scope scope
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue? api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue?
end end

View File

@ -20,7 +20,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def set_statuses def set_statuses
@statuses = if enabled? @statuses = if enabled?
cache_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status) preload_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
else else
[] []
end end
@ -32,10 +32,6 @@ class Api::V1::Trends::StatusesController < Api::BaseController
scope scope
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue? api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue?
end end

View File

@ -30,10 +30,6 @@ class Api::V1::Trends::TagsController < Api::BaseController
Trends.tags.query.allowed Trends.tags.query.allowed
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue? api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue?
end end

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
class Api::V2Alpha::NotificationsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss]
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss]
before_action :require_user!
after_action :insert_pagination_headers, only: :index
DEFAULT_NOTIFICATIONS_LIMIT = 40
def index
with_read_replica do
@notifications = load_notifications
@group_metadata = load_group_metadata
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
end
render json: @notifications.map { |notification| NotificationGroup.from_notification(notification) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata
end
def show
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:id])
render json: NotificationGroup.from_notification(@notification), serializer: REST::NotificationGroupSerializer
end
def clear
current_account.notifications.delete_all
render_empty
end
def dismiss
current_account.notifications.where(group_key: params[:id]).destroy_all
render_empty
end
private
def load_notifications
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_grouped_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
preload_collection(target_statuses, Status)
end
end
def load_group_metadata
return {} if @notifications.empty?
browserable_account_notifications
.where(group_key: @notifications.filter_map(&:group_key))
.where(id: (@notifications.last.id)..(@notifications.first.id))
.group(:group_key)
.pluck(:group_key, 'min(notifications.id) as min_id', 'max(notifications.id) as max_id', 'max(notifications.created_at) as latest_notification_at')
.to_h { |group_key, min_id, max_id, latest_notification_at| [group_key, { min_id: min_id, max_id: max_id, latest_notification_at: latest_notification_at }] }
end
def browserable_account_notifications
current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]),
exclude_types: Array(browserable_params[:exclude_types]),
include_filtered: truthy_param?(:include_filtered)
)
end
def target_statuses_from_notifications
@notifications.filter_map(&:target_status)
end
def next_path
api_v2_alpha_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty?
end
def prev_path
api_v2_alpha_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty?
end
def pagination_collection
@notifications
end
def browserable_params
params.permit(:include_filtered, types: [], exclude_types: [])
end
def pagination_params(core_params)
params.slice(:limit, :types, :exclude_types, :include_filtered).permit(:limit, :include_filtered, types: [], exclude_types: []).merge(core_params)
end
end

View File

@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern include UserTrackingConcern
include SessionTrackingConcern include SessionTrackingConcern
include CacheConcern include CacheConcern
include PreloadingConcern
include DomainControlHelper include DomainControlHelper
include ThemingConcern include ThemingConcern
include DatabaseHelper include DatabaseHelper

View File

@ -44,7 +44,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end end
def build_resource(hash = nil) def build_resource(hash = nil)
super(hash) super
resource.locale = I18n.locale resource.locale = I18n.locale
resource.invite_code = @invite&.code if resource.invite_code.blank? resource.invite_code = @invite&.code if resource.invite_code.blank?

View File

@ -3,6 +3,8 @@
module Api::Pagination module Api::Pagination
extend ActiveSupport::Concern extend ActiveSupport::Concern
PAGINATION_PARAMS = %i(limit).freeze
protected protected
def pagination_max_id def pagination_max_id
@ -24,6 +26,13 @@ module Api::Pagination
render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid? render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid?
end end
def pagination_params(core_params)
params
.slice(*PAGINATION_PARAMS)
.permit(*PAGINATION_PARAMS)
.merge(core_params)
end
private private
def insert_pagination_headers def insert_pagination_headers

View File

@ -45,20 +45,4 @@ module CacheConcern
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true) Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
end end
end end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:preload_cacheable_associations)
records = raw.to_a
klass.preload_cacheable_associations(records)
records
end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.to_a_paginated_by_id(limit, options), klass
end
end end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module PreloadingConcern
extend ActiveSupport::Concern
def preload_collection(scope, klass)
return scope unless klass.respond_to?(:preload_cacheable_associations)
scope.to_a.tap do |records|
klass.preload_cacheable_associations(records)
end
end
def preload_collection_paginated_by_id(scope, klass, limit, options)
preload_collection scope.to_a_paginated_by_id(limit, options), klass
end
end

View File

@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController
def new def new
@application = Doorkeeper::Application.new( @application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri, redirect_uri: Doorkeeper.configuration.native_redirect_uri,
scopes: 'read write follow' scopes: 'profile'
) )
end end

View File

@ -45,7 +45,7 @@ class TagsController < ApplicationController
end end
def set_statuses def set_statuses
@statuses = cache_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status) @statuses = preload_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status)
end end
def limit_param def limit_param

View File

@ -241,11 +241,20 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end end
def site_icon_path(type, size = '48') def mascot_url
icon = SiteUpload.find_by(var: type) full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
return nil unless icon end
icon.file.url(size) def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
def favicon_path(size = '48')
instance_presenter.favicon&.file&.url(size)
end
def app_icon_path(size = '48')
instance_presenter.app_icon&.file&.url(size)
end end
# glitch-soc addition to handle the multiple flavors # glitch-soc addition to handle the multiple flavors

View File

@ -1,11 +0,0 @@
# frozen_string_literal: true
module MascotHelper
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
end

View File

@ -65,7 +65,7 @@ window.addEventListener('message', (e) => {
{ {
type: 'setHeight', type: 'setHeight',
id: data.id, id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight, height: document.getElementsByTagName('html')[0]?.scrollHeight,
}, },
'*', '*',
); );
@ -135,7 +135,7 @@ function loaded() {
); );
}; };
const todayFormat = new IntlMessageFormat( const todayFormat = new IntlMessageFormat(
localeData['relative_format.today'] || 'Today at {time}', localeData['relative_format.today'] ?? 'Today at {time}',
locale, locale,
); );
@ -288,13 +288,13 @@ function loaded() {
if (statusEl.dataset.spoiler === 'expanded') { if (statusEl.dataset.spoiler === 'expanded') {
statusEl.dataset.spoiler = 'folded'; statusEl.dataset.spoiler = 'folded';
this.textContent = new IntlMessageFormat( this.textContent = new IntlMessageFormat(
localeData['status.show_more'] || 'Show more', localeData['status.show_more'] ?? 'Show more',
locale, locale,
).format() as string; ).format() as string;
} else { } else {
statusEl.dataset.spoiler = 'expanded'; statusEl.dataset.spoiler = 'expanded';
this.textContent = new IntlMessageFormat( this.textContent = new IntlMessageFormat(
localeData['status.show_less'] || 'Show less', localeData['status.show_less'] ?? 'Show less',
locale, locale,
).format() as string; ).format() as string;
} }
@ -316,8 +316,8 @@ function loaded() {
const message = const message =
statusEl.dataset.spoiler === 'expanded' statusEl.dataset.spoiler === 'expanded'
? localeData['status.show_less'] || 'Show less' ? localeData['status.show_less'] ?? 'Show less'
: localeData['status.show_more'] || 'Show more'; : localeData['status.show_more'] ?? 'Show more';
spoilerLink.textContent = new IntlMessageFormat( spoilerLink.textContent = new IntlMessageFormat(
message, message,
locale, locale,

View File

@ -67,7 +67,9 @@ const fetchInteractionURLFailure = () => {
); );
}; };
const isValidDomain = (value: string) => { const isValidDomain = (value: unknown) => {
if (typeof value !== 'string') return false;
const url = new URL('https:///path'); const url = new URL('https:///path');
url.hostname = value; url.hostname = value;
return url.hostname === value; return url.hostname === value;
@ -124,6 +126,11 @@ const fromAcct = (acct: string) => {
const domain = segments[1]; const domain = segments[1];
const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;
if (!domain) {
fetchInteractionURLFailure();
return;
}
axios axios
.get(`https://${domain}/.well-known/webfinger`, { .get(`https://${domain}/.well-known/webfinger`, {
params: { resource: `acct:${acct}` }, params: { resource: `acct:${acct}` },

View File

@ -1,18 +1,10 @@
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships'; import { apiSubmitAccountNote } from 'flavours/glitch/api/accounts';
import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions'; import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';
import api from '../api'; export const submitAccountNote = createDataLoadingThunk(
export const submitAccountNote = createAppAsyncThunk(
'account_note/submit', 'account_note/submit',
async (args: { id: string; value: string }, { getState }) => { ({ accountId, note }: { accountId: string; note: string }) =>
const response = await api(getState).post<ApiRelationshipJSON>( apiSubmitAccountNote(accountId, note),
`/api/v1/accounts/${args.id}/note`, (relationship) => ({ relationship }),
{ { skipLoading: true },
comment: args.value,
},
);
return { relationship: response.data };
},
); );

View File

@ -89,11 +89,11 @@ export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
export * from './accounts_typed'; export * from './accounts_typed';
export function fetchAccount(id) { export function fetchAccount(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchRelationships([id])); dispatch(fetchRelationships([id]));
dispatch(fetchAccountRequest(id)); dispatch(fetchAccountRequest(id));
api(getState).get(`/api/v1/accounts/${id}`).then(response => { api().get(`/api/v1/accounts/${id}`).then(response => {
dispatch(importFetchedAccount(response.data)); dispatch(importFetchedAccount(response.data));
dispatch(fetchAccountSuccess()); dispatch(fetchAccountSuccess());
}).catch(error => { }).catch(error => {
@ -102,10 +102,10 @@ export function fetchAccount(id) {
}; };
} }
export const lookupAccount = acct => (dispatch, getState) => { export const lookupAccount = acct => (dispatch) => {
dispatch(lookupAccountRequest(acct)); dispatch(lookupAccountRequest(acct));
api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { api().get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
dispatch(fetchRelationships([response.data.id])); dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data)); dispatch(importFetchedAccount(response.data));
dispatch(lookupAccountSuccess()); dispatch(lookupAccountSuccess());
@ -159,7 +159,7 @@ export function followAccount(id, options = { reblogs: true }) {
dispatch(followAccountRequest({ id, locked })); dispatch(followAccountRequest({ id, locked }));
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => { api().post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing})); dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing}));
}).catch(error => { }).catch(error => {
dispatch(followAccountFail({ id, error, locked })); dispatch(followAccountFail({ id, error, locked }));
@ -171,7 +171,7 @@ export function unfollowAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(unfollowAccountRequest(id)); dispatch(unfollowAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { api().post(`/api/v1/accounts/${id}/unfollow`).then(response => {
dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')})); dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')}));
}).catch(error => { }).catch(error => {
dispatch(unfollowAccountFail({ id, error })); dispatch(unfollowAccountFail({ id, error }));
@ -183,7 +183,7 @@ export function blockAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(blockAccountRequest(id)); dispatch(blockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { api().post(`/api/v1/accounts/${id}/block`).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => { }).catch(error => {
@ -193,10 +193,10 @@ export function blockAccount(id) {
} }
export function unblockAccount(id) { export function unblockAccount(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unblockAccountRequest(id)); dispatch(unblockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { api().post(`/api/v1/accounts/${id}/unblock`).then(response => {
dispatch(unblockAccountSuccess({ relationship: response.data })); dispatch(unblockAccountSuccess({ relationship: response.data }));
}).catch(error => { }).catch(error => {
dispatch(unblockAccountFail({ id, error })); dispatch(unblockAccountFail({ id, error }));
@ -236,7 +236,7 @@ export function muteAccount(id, notifications, duration=0) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(muteAccountRequest(id)); dispatch(muteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { api().post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => { }).catch(error => {
@ -246,10 +246,10 @@ export function muteAccount(id, notifications, duration=0) {
} }
export function unmuteAccount(id) { export function unmuteAccount(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unmuteAccountRequest(id)); dispatch(unmuteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { api().post(`/api/v1/accounts/${id}/unmute`).then(response => {
dispatch(unmuteAccountSuccess({ relationship: response.data })); dispatch(unmuteAccountSuccess({ relationship: response.data }));
}).catch(error => { }).catch(error => {
dispatch(unmuteAccountFail({ id, error })); dispatch(unmuteAccountFail({ id, error }));
@ -287,10 +287,10 @@ export function unmuteAccountFail(error) {
export function fetchFollowers(id) { export function fetchFollowers(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchFollowersRequest(id)); dispatch(fetchFollowersRequest(id));
api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { api().get(`/api/v1/accounts/${id}/followers`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -337,7 +337,7 @@ export function expandFollowers(id) {
dispatch(expandFollowersRequest(id)); dispatch(expandFollowersRequest(id));
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -374,10 +374,10 @@ export function expandFollowersFail(id, error) {
} }
export function fetchFollowing(id) { export function fetchFollowing(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchFollowingRequest(id)); dispatch(fetchFollowingRequest(id));
api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { api().get(`/api/v1/accounts/${id}/following`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -424,7 +424,7 @@ export function expandFollowing(id) {
dispatch(expandFollowingRequest(id)); dispatch(expandFollowingRequest(id));
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -473,7 +473,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsRequest(newAccountIds)); dispatch(fetchRelationshipsRequest(newAccountIds));
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { api().get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess({ relationships: response.data })); dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
}).catch(error => { }).catch(error => {
dispatch(fetchRelationshipsFail(error)); dispatch(fetchRelationshipsFail(error));
@ -499,10 +499,10 @@ export function fetchRelationshipsFail(error) {
} }
export function fetchFollowRequests() { export function fetchFollowRequests() {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchFollowRequestsRequest()); dispatch(fetchFollowRequestsRequest());
api(getState).get('/api/v1/follow_requests').then(response => { api().get('/api/v1/follow_requests').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null));
@ -541,7 +541,7 @@ export function expandFollowRequests() {
dispatch(expandFollowRequestsRequest()); dispatch(expandFollowRequestsRequest());
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null));
@ -571,10 +571,10 @@ export function expandFollowRequestsFail(error) {
} }
export function authorizeFollowRequest(id) { export function authorizeFollowRequest(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(authorizeFollowRequestRequest(id)); dispatch(authorizeFollowRequestRequest(id));
api(getState) api()
.post(`/api/v1/follow_requests/${id}/authorize`) .post(`/api/v1/follow_requests/${id}/authorize`)
.then(() => dispatch(authorizeFollowRequestSuccess({ id }))) .then(() => dispatch(authorizeFollowRequestSuccess({ id })))
.catch(error => dispatch(authorizeFollowRequestFail(id, error))); .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
@ -598,10 +598,10 @@ export function authorizeFollowRequestFail(id, error) {
export function rejectFollowRequest(id) { export function rejectFollowRequest(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(rejectFollowRequestRequest(id)); dispatch(rejectFollowRequestRequest(id));
api(getState) api()
.post(`/api/v1/follow_requests/${id}/reject`) .post(`/api/v1/follow_requests/${id}/reject`)
.then(() => dispatch(rejectFollowRequestSuccess({ id }))) .then(() => dispatch(rejectFollowRequestSuccess({ id })))
.catch(error => dispatch(rejectFollowRequestFail(id, error))); .catch(error => dispatch(rejectFollowRequestFail(id, error)));
@ -624,10 +624,10 @@ export function rejectFollowRequestFail(id, error) {
} }
export function pinAccount(id) { export function pinAccount(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(pinAccountRequest(id)); dispatch(pinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => { api().post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess({ relationship: response.data })); dispatch(pinAccountSuccess({ relationship: response.data }));
}).catch(error => { }).catch(error => {
dispatch(pinAccountFail(error)); dispatch(pinAccountFail(error));
@ -636,10 +636,10 @@ export function pinAccount(id) {
} }
export function unpinAccount(id) { export function unpinAccount(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unpinAccountRequest(id)); dispatch(unpinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => { api().post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess({ relationship: response.data })); dispatch(unpinAccountSuccess({ relationship: response.data }));
}).catch(error => { }).catch(error => {
dispatch(unpinAccountFail(error)); dispatch(unpinAccountFail(error));
@ -676,10 +676,10 @@ export function unpinAccountFail(error) {
} }
export function fetchPinnedAccounts() { export function fetchPinnedAccounts() {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchPinnedAccountsRequest()); dispatch(fetchPinnedAccountsRequest());
api(getState).get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => { api().get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => {
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchPinnedAccountsSuccess(response.data)); dispatch(fetchPinnedAccountsSuccess(response.data));
}).catch(err => dispatch(fetchPinnedAccountsFail(err))); }).catch(err => dispatch(fetchPinnedAccountsFail(err)));
@ -707,7 +707,7 @@ export function fetchPinnedAccountsFail(error) {
}; };
} }
export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => { export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch) => {
const data = new FormData(); const data = new FormData();
data.append('display_name', displayName); data.append('display_name', displayName);
@ -717,13 +717,13 @@ export const updateAccount = ({ displayName, note, avatar, header, discoverable,
data.append('discoverable', discoverable); data.append('discoverable', discoverable);
data.append('indexable', indexable); data.append('indexable', indexable);
return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => { return api().patch('/api/v1/accounts/update_credentials', data).then(response => {
dispatch(importFetchedAccount(response.data)); dispatch(importFetchedAccount(response.data));
}); });
}; };
export function fetchPinnedAccountsSuggestions(q) { export function fetchPinnedAccountsSuggestions(q) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchPinnedAccountsSuggestionsRequest()); dispatch(fetchPinnedAccountsSuggestionsRequest());
const params = { const params = {
@ -733,7 +733,7 @@ export function fetchPinnedAccountsSuggestions(q) {
following: true, following: true,
}; };
api(getState).get('/api/v1/accounts/search', { params }).then(response => { api().get('/api/v1/accounts/search', { params }).then(response => {
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchPinnedAccountsSuggestionsSuccess(q, response.data)); dispatch(fetchPinnedAccountsSuggestionsSuccess(q, response.data));
}).catch(err => dispatch(fetchPinnedAccountsSuggestionsFail(err))); }).catch(err => dispatch(fetchPinnedAccountsSuggestionsFail(err)));

View File

@ -26,10 +26,10 @@ export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
const noOp = () => {}; const noOp = () => {};
export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => { export const fetchAnnouncements = (done = noOp) => (dispatch) => {
dispatch(fetchAnnouncementsRequest()); dispatch(fetchAnnouncementsRequest());
api(getState).get('/api/v1/announcements').then(response => { api().get('/api/v1/announcements').then(response => {
dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x)))); dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
}).catch(error => { }).catch(error => {
dispatch(fetchAnnouncementsFail(error)); dispatch(fetchAnnouncementsFail(error));
@ -61,10 +61,10 @@ export const updateAnnouncements = announcement => ({
announcement: normalizeAnnouncement(announcement), announcement: normalizeAnnouncement(announcement),
}); });
export const dismissAnnouncement = announcementId => (dispatch, getState) => { export const dismissAnnouncement = announcementId => (dispatch) => {
dispatch(dismissAnnouncementRequest(announcementId)); dispatch(dismissAnnouncementRequest(announcementId));
api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => { api().post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => {
dispatch(dismissAnnouncementSuccess(announcementId)); dispatch(dismissAnnouncementSuccess(announcementId));
}).catch(error => { }).catch(error => {
dispatch(dismissAnnouncementFail(announcementId, error)); dispatch(dismissAnnouncementFail(announcementId, error));
@ -103,7 +103,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name, alreadyAdded)); dispatch(addReactionRequest(announcementId, name, alreadyAdded));
} }
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { api().put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(announcementId, name, alreadyAdded)); dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
}).catch(err => { }).catch(err => {
if (!alreadyAdded) { if (!alreadyAdded) {
@ -134,10 +134,10 @@ export const addReactionFail = (announcementId, name, error) => ({
skipLoading: true, skipLoading: true,
}); });
export const removeReaction = (announcementId, name) => (dispatch, getState) => { export const removeReaction = (announcementId, name) => (dispatch) => {
dispatch(removeReactionRequest(announcementId, name)); dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { api().delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name)); dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => { }).catch(err => {
dispatch(removeReactionFail(announcementId, name, err)); dispatch(removeReactionFail(announcementId, name, err));

View File

@ -13,10 +13,10 @@ export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';
export function fetchBlocks() { export function fetchBlocks() {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchBlocksRequest()); dispatch(fetchBlocksRequest());
api(getState).get('/api/v1/blocks').then(response => { api().get('/api/v1/blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
@ -56,7 +56,7 @@ export function expandBlocks() {
dispatch(expandBlocksRequest()); dispatch(expandBlocksRequest());
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));

View File

@ -18,7 +18,7 @@ export function fetchBookmarkedStatuses() {
dispatch(fetchBookmarkedStatusesRequest()); dispatch(fetchBookmarkedStatusesRequest());
api(getState).get('/api/v1/bookmarks').then(response => { api().get('/api/v1/bookmarks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
@ -59,7 +59,7 @@ export function expandBookmarkedStatuses() {
dispatch(expandBookmarkedStatusesRequest()); dispatch(expandBookmarkedStatusesRequest());
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));

View File

@ -211,7 +211,7 @@ export function submitCompose(routerHistory, overridePrivacy = null) {
}); });
} }
api(getState).request({ api().request({
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
method: statusId === null ? 'post' : 'put', method: statusId === null ? 'post' : 'put',
data: { data: {
@ -338,7 +338,7 @@ export function uploadCompose(files) {
// Account for disparity in size of original image and resized data // Account for disparity in size of original image and resized data
total += file.size - f.size; total += file.size - f.size;
return api(getState).post('/api/v2/media', data, { return api().post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){ onUploadProgress: function({ loaded }){
progress[i] = loaded; progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
@ -355,7 +355,7 @@ export function uploadCompose(files) {
let tryCount = 1; let tryCount = 1;
const poll = () => { const poll = () => {
api(getState).get(`/api/v1/media/${data.id}`).then(response => { api().get(`/api/v1/media/${data.id}`).then(response => {
if (response.status === 200) { if (response.status === 200) {
dispatch(uploadComposeSuccess(response.data, f)); dispatch(uploadComposeSuccess(response.data, f));
} else if (response.status === 206) { } else if (response.status === 206) {
@ -378,7 +378,7 @@ export const uploadComposeProcessing = () => ({
type: COMPOSE_UPLOAD_PROCESSING, type: COMPOSE_UPLOAD_PROCESSING,
}); });
export const uploadThumbnail = (id, file) => (dispatch, getState) => { export const uploadThumbnail = (id, file) => (dispatch) => {
dispatch(uploadThumbnailRequest()); dispatch(uploadThumbnailRequest());
const total = file.size; const total = file.size;
@ -386,7 +386,7 @@ export const uploadThumbnail = (id, file) => (dispatch, getState) => {
data.append('thumbnail', file); data.append('thumbnail', file);
api(getState).put(`/api/v1/media/${id}`, data, { api().put(`/api/v1/media/${id}`, data, {
onUploadProgress: ({ loaded }) => { onUploadProgress: ({ loaded }) => {
dispatch(uploadThumbnailProgress(loaded, total)); dispatch(uploadThumbnailProgress(loaded, total));
}, },
@ -469,7 +469,7 @@ export function changeUploadCompose(id, params) {
dispatch(changeUploadComposeSuccess(data, true)); dispatch(changeUploadComposeSuccess(data, true));
} else { } else {
api(getState).put(`/api/v1/media/${id}`, params).then(response => { api().put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data, false)); dispatch(changeUploadComposeSuccess(response.data, false));
}).catch(error => { }).catch(error => {
dispatch(changeUploadComposeFail(id, error)); dispatch(changeUploadComposeFail(id, error));
@ -557,7 +557,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
fetchComposeSuggestionsAccountsController = new AbortController(); fetchComposeSuggestionsAccountsController = new AbortController();
api(getState).get('/api/v1/accounts/search', { api().get('/api/v1/accounts/search', {
signal: fetchComposeSuggestionsAccountsController.signal, signal: fetchComposeSuggestionsAccountsController.signal,
params: { params: {
@ -591,7 +591,7 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
fetchComposeSuggestionsTagsController = new AbortController(); fetchComposeSuggestionsTagsController = new AbortController();
api(getState).get('/api/v2/search', { api().get('/api/v2/search', {
signal: fetchComposeSuggestionsTagsController.signal, signal: fetchComposeSuggestionsTagsController.signal,
params: { params: {

View File

@ -28,13 +28,13 @@ export const unmountConversations = () => ({
type: CONVERSATIONS_UNMOUNT, type: CONVERSATIONS_UNMOUNT,
}); });
export const markConversationRead = conversationId => (dispatch, getState) => { export const markConversationRead = conversationId => (dispatch) => {
dispatch({ dispatch({
type: CONVERSATIONS_READ, type: CONVERSATIONS_READ,
id: conversationId, id: conversationId,
}); });
api(getState).post(`/api/v1/conversations/${conversationId}/read`); api().post(`/api/v1/conversations/${conversationId}/read`);
}; };
export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
@ -48,7 +48,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
const isLoadingRecent = !!params.since_id; const isLoadingRecent = !!params.since_id;
api(getState).get('/api/v1/conversations', { params }) api().get('/api/v1/conversations', { params })
.then(response => { .then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
@ -88,10 +88,10 @@ export const updateConversations = conversation => dispatch => {
}); });
}; };
export const deleteConversation = conversationId => (dispatch, getState) => { export const deleteConversation = conversationId => (dispatch) => {
dispatch(deleteConversationRequest(conversationId)); dispatch(deleteConversationRequest(conversationId));
api(getState).delete(`/api/v1/conversations/${conversationId}`) api().delete(`/api/v1/conversations/${conversationId}`)
.then(() => dispatch(deleteConversationSuccess(conversationId))) .then(() => dispatch(deleteConversationSuccess(conversationId)))
.catch(error => dispatch(deleteConversationFail(conversationId, error))); .catch(error => dispatch(deleteConversationFail(conversationId, error)));
}; };

View File

@ -5,10 +5,10 @@ export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL'; export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
export function fetchCustomEmojis() { export function fetchCustomEmojis() {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchCustomEmojisRequest()); dispatch(fetchCustomEmojisRequest());
api(getState).get('/api/v1/custom_emojis').then(response => { api().get('/api/v1/custom_emojis').then(response => {
dispatch(fetchCustomEmojisSuccess(response.data)); dispatch(fetchCustomEmojisSuccess(response.data));
}).catch(error => { }).catch(error => {
dispatch(fetchCustomEmojisFail(error)); dispatch(fetchCustomEmojisFail(error));

View File

@ -11,10 +11,10 @@ export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS'; export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL'; export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
export const fetchDirectory = params => (dispatch, getState) => { export const fetchDirectory = params => (dispatch) => {
dispatch(fetchDirectoryRequest()); dispatch(fetchDirectoryRequest());
api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => { api().get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data)); dispatch(importFetchedAccounts(data));
dispatch(fetchDirectorySuccess(data)); dispatch(fetchDirectorySuccess(data));
dispatch(fetchRelationships(data.map(x => x.id))); dispatch(fetchRelationships(data.map(x => x.id)));
@ -40,7 +40,7 @@ export const expandDirectory = params => (dispatch, getState) => {
const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size; const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => { api().get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data)); dispatch(importFetchedAccounts(data));
dispatch(expandDirectorySuccess(data)); dispatch(expandDirectorySuccess(data));
dispatch(fetchRelationships(data.map(x => x.id))); dispatch(fetchRelationships(data.map(x => x.id)));

View File

@ -24,7 +24,7 @@ export function blockDomain(domain) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(blockDomainRequest(domain)); dispatch(blockDomainRequest(domain));
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { api().post('/api/v1/domain_blocks', { domain }).then(() => {
const at_domain = '@' + domain; const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
@ -54,7 +54,7 @@ export function unblockDomain(domain) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(unblockDomainRequest(domain)); dispatch(unblockDomainRequest(domain));
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { api().delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
const at_domain = '@' + domain; const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
dispatch(unblockDomainSuccess({ domain, accounts })); dispatch(unblockDomainSuccess({ domain, accounts }));
@ -80,10 +80,10 @@ export function unblockDomainFail(domain, error) {
} }
export function fetchDomainBlocks() { export function fetchDomainBlocks() {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchDomainBlocksRequest()); dispatch(fetchDomainBlocksRequest());
api(getState).get('/api/v1/domain_blocks').then(response => { api().get('/api/v1/domain_blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
}).catch(err => { }).catch(err => {
@ -123,7 +123,7 @@ export function expandDomainBlocks() {
dispatch(expandDomainBlocksRequest()); dispatch(expandDomainBlocksRequest());
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null)); dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null));
}).catch(err => { }).catch(err => {

View File

@ -18,7 +18,7 @@ export function fetchFavouritedStatuses() {
dispatch(fetchFavouritedStatusesRequest()); dispatch(fetchFavouritedStatusesRequest());
api(getState).get('/api/v1/favourites').then(response => { api().get('/api/v1/favourites').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
@ -62,7 +62,7 @@ export function expandFavouritedStatuses() {
dispatch(expandFavouritedStatusesRequest()); dispatch(expandFavouritedStatusesRequest());
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));

View File

@ -11,7 +11,7 @@ export const fetchFeaturedTags = (id) => (dispatch, getState) => {
dispatch(fetchFeaturedTagsRequest(id)); dispatch(fetchFeaturedTagsRequest(id));
api(getState).get(`/api/v1/accounts/${id}/featured_tags`) api().get(`/api/v1/accounts/${id}/featured_tags`)
.then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data))) .then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data)))
.catch(err => dispatch(fetchFeaturedTagsFail(id, err))); .catch(err => dispatch(fetchFeaturedTagsFail(id, err)));
}; };

View File

@ -23,13 +23,13 @@ export const initAddFilter = (status, { contextType }) => dispatch =>
}, },
})); }));
export const fetchFilters = () => (dispatch, getState) => { export const fetchFilters = () => (dispatch) => {
dispatch({ dispatch({
type: FILTERS_FETCH_REQUEST, type: FILTERS_FETCH_REQUEST,
skipLoading: true, skipLoading: true,
}); });
api(getState) api()
.get('/api/v2/filters') .get('/api/v2/filters')
.then(({ data }) => dispatch({ .then(({ data }) => dispatch({
type: FILTERS_FETCH_SUCCESS, type: FILTERS_FETCH_SUCCESS,
@ -44,10 +44,10 @@ export const fetchFilters = () => (dispatch, getState) => {
})); }));
}; };
export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => { export const createFilterStatus = (params, onSuccess, onFail) => (dispatch) => {
dispatch(createFilterStatusRequest()); dispatch(createFilterStatusRequest());
api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { api().post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => {
dispatch(createFilterStatusSuccess(response.data)); dispatch(createFilterStatusSuccess(response.data));
if (onSuccess) onSuccess(); if (onSuccess) onSuccess();
}).catch(error => { }).catch(error => {
@ -70,10 +70,10 @@ export const createFilterStatusFail = error => ({
error, error,
}); });
export const createFilter = (params, onSuccess, onFail) => (dispatch, getState) => { export const createFilter = (params, onSuccess, onFail) => (dispatch) => {
dispatch(createFilterRequest()); dispatch(createFilterRequest());
api(getState).post('/api/v2/filters', params).then(response => { api().post('/api/v2/filters', params).then(response => {
dispatch(createFilterSuccess(response.data)); dispatch(createFilterSuccess(response.data));
if (onSuccess) onSuccess(response.data); if (onSuccess) onSuccess(response.data);
}).catch(error => { }).catch(error => {

View File

@ -15,7 +15,7 @@ export const fetchHistory = statusId => (dispatch, getState) => {
dispatch(fetchHistoryRequest(statusId)); dispatch(fetchHistoryRequest(statusId));
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => { api().get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
dispatch(importFetchedAccounts(data.map(x => x.account))); dispatch(importFetchedAccounts(data.map(x => x.account)));
dispatch(fetchHistorySuccess(statusId, data)); dispatch(fetchHistorySuccess(statusId, data));
}).catch(error => dispatch(fetchHistoryFail(error))); }).catch(error => dispatch(fetchHistoryFail(error)));

View File

@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer'; import { importFetchedAccounts, importFetchedStatus } from './importer';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
export const REBLOG_FAIL = 'REBLOG_FAIL';
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST'; export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS'; export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL'; export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
@ -15,10 +11,6 @@ export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; export const FAVOURITE_FAIL = 'FAVOURITE_FAIL';
export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
export const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
@ -51,99 +43,13 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL'; export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
export const REACTION_UPDATE = 'REACTION_UPDATE'; export * from "./interactions_typed";
export const REACTION_ADD_REQUEST = 'REACTION_ADD_REQUEST';
export const REACTION_ADD_SUCCESS = 'REACTION_ADD_SUCCESS';
export const REACTION_ADD_FAIL = 'REACTION_ADD_FAIL';
export const REACTION_REMOVE_REQUEST = 'REACTION_REMOVE_REQUEST';
export const REACTION_REMOVE_SUCCESS = 'REACTION_REMOVE_SUCCESS';
export const REACTION_REMOVE_FAIL = 'REACTION_REMOVE_FAIL';
export function reblog(status, visibility) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog));
dispatch(reblogSuccess(status));
}).catch(function (error) {
dispatch(reblogFail(status, error));
});
};
}
export function unreblog(status) {
return (dispatch, getState) => {
dispatch(unreblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unreblogSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
});
};
}
export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
export function reblogSuccess(status) {
return {
type: REBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
export function reblogFail(status, error) {
return {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
export function unreblogSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
export function unreblogFail(status, error) {
return {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
export function favourite(status) { export function favourite(status) {
return function (dispatch, getState) { return function (dispatch) {
dispatch(favouriteRequest(status)); dispatch(favouriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { api().post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(favouriteSuccess(status)); dispatch(favouriteSuccess(status));
}).catch(function (error) { }).catch(function (error) {
@ -153,10 +59,10 @@ export function favourite(status) {
} }
export function unfavourite(status) { export function unfavourite(status) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unfavouriteRequest(status)); dispatch(unfavouriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { api().post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(unfavouriteSuccess(status)); dispatch(unfavouriteSuccess(status));
}).catch(error => { }).catch(error => {
@ -216,10 +122,10 @@ export function unfavouriteFail(status, error) {
} }
export function bookmark(status) { export function bookmark(status) {
return function (dispatch, getState) { return function (dispatch) {
dispatch(bookmarkRequest(status)); dispatch(bookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) { api().post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status, response.data)); dispatch(bookmarkSuccess(status, response.data));
}).catch(function (error) { }).catch(function (error) {
@ -229,10 +135,10 @@ export function bookmark(status) {
} }
export function unbookmark(status) { export function unbookmark(status) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unbookmarkRequest(status)); dispatch(unbookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => { api().post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status, response.data)); dispatch(unbookmarkSuccess(status, response.data));
}).catch(error => { }).catch(error => {
@ -288,10 +194,10 @@ export function unbookmarkFail(status, error) {
} }
export function fetchReblogs(id) { export function fetchReblogs(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchReblogsRequest(id)); dispatch(fetchReblogsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { api().get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null));
@ -335,7 +241,7 @@ export function expandReblogs(id) {
dispatch(expandReblogsRequest(id)); dispatch(expandReblogsRequest(id));
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -370,10 +276,10 @@ export function expandReblogsFail(id, error) {
} }
export function fetchFavourites(id) { export function fetchFavourites(id) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(fetchFavouritesRequest(id)); dispatch(fetchFavouritesRequest(id));
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { api().get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null)); dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null));
@ -417,7 +323,7 @@ export function expandFavourites(id) {
dispatch(expandFavouritesRequest(id)); dispatch(expandFavouritesRequest(id));
api(getState).get(url).then(response => { api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -452,10 +358,10 @@ export function expandFavouritesFail(id, error) {
} }
export function pin(status) { export function pin(status) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(pinRequest(status)); dispatch(pinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(pinSuccess(status)); dispatch(pinSuccess(status));
}).catch(error => { }).catch(error => {
@ -490,10 +396,10 @@ export function pinFail(status, error) {
} }
export function unpin (status) { export function unpin (status) {
return (dispatch, getState) => { return (dispatch) => {
dispatch(unpinRequest(status)); dispatch(unpinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
dispatch(importFetchedStatus(response.data)); dispatch(importFetchedStatus(response.data));
dispatch(unpinSuccess(status)); dispatch(unpinSuccess(status));
}).catch(error => { }).catch(error => {

View File

@ -0,0 +1,35 @@
import { apiReblog, apiUnreblog } from 'flavours/glitch/api/interactions';
import type { StatusVisibility } from 'flavours/glitch/models/status';
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';
import { importFetchedStatus } from './importer';
export const reblog = createDataLoadingThunk(
'status/reblog',
({
statusId,
visibility,
}: {
statusId: string;
visibility: StatusVisibility;
}) => apiReblog(statusId, visibility),
(data, { dispatch, discardLoadData }) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(data.reblog));
// The payload is not used in any actions
return discardLoadData;
},
);
export const unreblog = createDataLoadingThunk(
'status/unreblog',
({ statusId }: { statusId: string }) => apiUnreblog(statusId),
(data, { dispatch, discardLoadData }) => {
dispatch(importFetchedStatus(data));
// The payload is not used in any actions
return discardLoadData;
},
);

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