Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
8287d6df9e Bump axios from 1.5.1 to 1.6.0
Bumps [axios](https://github.com/axios/axios) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-11 16:09:12 +00:00
1082 changed files with 22971 additions and 33720 deletions

View File

@@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
# Install Rails
# RUN gem install rails webdrivers
ARG NODE_VERSION="20"
ARG NODE_VERSION="16"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
# [Optional] Uncomment this section to install additional OS packages.
@@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
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
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

View File

@@ -70,7 +70,7 @@ services:
hard: -1
libretranslate:
image: libretranslate/libretranslate:v1.5.2
image: libretranslate/libretranslate:v1.3.12
restart: unless-stopped
volumes:
- lt-data:/home/libretranslate/.local

View File

@@ -11,8 +11,7 @@ bundle install
git checkout -- Gemfile.lock
# Fetch Javascript dependencies
corepack prepare
yarn install --immutable
yarn --frozen-lockfile
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup

View File

@@ -8,7 +8,6 @@
public/system
public/assets
public/packs
public/packs-test
node_modules
neo4j
vendor/bundle

View File

@@ -1,5 +1,5 @@
# In test, compile the NodeJS code as if we are in production
NODE_ENV=production
# Node.js
NODE_ENV=tests
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true

View File

@@ -9,34 +9,11 @@ runs:
using: 'composite'
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
# The following is needed because we can not use `cache: true` for `setup-node`, as it does not support Corepack yet and mess up with the cache location if ran after Node is installed
- name: Enable corepack
shell: bash
run: corepack enable
- name: Get yarn cache directory path
id: yarn-cache-dir-path
shell: bash
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install all yarn packages
shell: bash
run: yarn install --immutable
if: inputs.onlyProduction == 'false'
- name: Install all production yarn packages
shell: bash
run: yarn workspaces focus --production
if: inputs.onlyProduction != 'false'
run: yarn --frozen-lockfile ${{ inputs.onlyProduction != 'false' && '--production' || '' }}

13
.github/codecov.yml vendored
View File

@@ -1,13 +0,0 @@
coverage:
status:
project:
default:
# Github status check is not blocking
informational: true
patch:
default:
# Github status check is not blocking
informational: true
comment:
# Only write a comment in PR if there are changes
require_changes: true

View File

@@ -12,7 +12,6 @@
// If we do not want a package to be grouped with others, we need to set its groupName
// to `null` after any other rule set it to something.
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
postUpdateOptions: ['yarnDedupeHighest'],
packageRules: [
{
// Require Dependency Dashboard Approval for major version bumps of these node packages
@@ -22,7 +21,6 @@
'react-hotkeys', // Requires code changes
// Requires Webpacker upgrade or replacement
'@svgr/webpack',
'@types/webpack',
'babel-loader',
'compression-webpack-plugin',

View File

@@ -48,15 +48,12 @@ jobs:
run: |-
./bin/rails assets:precompile
- name: Archive asset artifacts
run: |
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
- uses: actions/upload-artifact@v3
if: matrix.mode == 'test'
with:
path: |-
./artifacts.tar.gz
./public/assets
./public/packs-test
name: ${{ github.sha }}
retention-days: 0
@@ -94,7 +91,7 @@ jobs:
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
DISABLE_SIMPLECOV: true
RAILS_ENV: test
ALLOW_NOPAM: true
PAM_ENABLED: true
@@ -105,6 +102,7 @@ jobs:
SAML_ENABLED: true
CAS_ENABLED: true
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
strategy:
@@ -114,18 +112,19 @@ jobs:
- '3.0'
- '3.1'
- '.ruby-version'
ci_job:
- 1
- 2
- 3
- 4
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
path: './'
path: './public'
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:
@@ -135,13 +134,7 @@ jobs:
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bin/rspec
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@v3
with:
files: coverage/lcov/mastodon.lcov
- run: bundle exec rake rspec_chunked
test-e2e:
name: End to End testing
@@ -227,7 +220,7 @@ jobs:
path: tmp/screenshots/
test-search:
name: Elastic Search integration testing
name: Testing search
runs-on: ubuntu-latest
needs:
@@ -314,7 +307,7 @@ jobs:
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bin/rspec --tag search
- run: bundle exec rake spec:search
- name: Archive logs
uses: actions/upload-artifact@v3

9
.gitignore vendored
View File

@@ -55,15 +55,6 @@ npm-debug.log
yarn-error.log
yarn-debug.log
# From https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Ignore vagrant log files
*-cloudimg-console.log

View File

@@ -12,5 +12,3 @@ linters:
enabled: true
MiddleDot:
enabled: true
LineLength:
max: 320

View File

@@ -1,33 +1,21 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2023-10-26 09:32:34 -0400 using Haml-Lint version 0.51.0.
# on 2023-10-25 08:29:48 -0400 using Haml-Lint version 0.51.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 16
# Offense count: 945
LineLength:
exclude:
- 'app/views/admin/account_actions/new.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/ip_blocks/new.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/admin/settings/discovery/show.html.haml'
- 'app/views/auth/registrations/edit.html.haml'
- 'app/views/auth/registrations/new.html.haml'
- 'app/views/filters/_filter_fields.html.haml'
- 'app/views/media/player.html.haml'
- 'app/views/settings/applications/_fields.html.haml'
- 'app/views/settings/imports/index.html.haml'
- 'app/views/settings/preferences/appearance/show.html.haml'
- 'app/views/settings/preferences/notifications/show.html.haml'
- 'app/views/settings/preferences/other/show.html.haml'
enabled: false
# Offense count: 9
# Offense count: 10
RuboCop:
exclude:
- 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/layouts/application.html.haml'

View File

@@ -27,7 +27,7 @@ AllCops:
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'config/initializers/json_ld*' # Generated files
- 'lib/json_ld/*' # Generated files
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
- 'lib/templates/**/*'
@@ -109,11 +109,16 @@ Rails/Exit:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
RSpec/FilePath:
CustomTransform:
ActivityPub: activitypub
ActivityPub: activitypub # Ignore the snake_case due to the amount of files to rename
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
JsonLdHelper: jsonld_helper
OEmbedController: oembed_controller
OStatus: ostatus
NodeInfoController: nodeinfo_controller # NodeInfo isn't snake_cased for any of the instances
Exclude:
- 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder
- 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder
# Reason:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
@@ -130,16 +135,6 @@ RSpec/NotToNot:
RSpec/Rails/HttpStatus:
EnforcedStyle: numeric
# Reason: Match overrides from Rspec/FilePath rule above
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat
RSpec/SpecFilePathFormat:
CustomTransform:
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
OEmbedController: oembed_controller
OStatus: ostatus
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
Style/ClassAndModuleChildren:

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.57.2.
# using RuboCop version 1.57.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -20,10 +20,34 @@ Layout/LineLength:
Exclude:
- 'app/models/account.rb'
# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'spec/controllers/api/v2/search_controller_spec.rb'
- 'spec/fabricators/access_token_fabricator.rb'
- 'spec/fabricators/conversation_fabricator.rb'
- 'spec/fabricators/system_key_fabricator.rb'
- 'spec/lib/activitypub/adapter_spec.rb'
- 'spec/models/user_role_spec.rb'
Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/jsonld_helper.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/OrAssignmentToConstant:
Exclude:
- 'lib/sanitize_ext/sanitize_config.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/simple_form.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
@@ -43,12 +67,76 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 27
Performance/MapMethodChain:
Exclude:
- 'app/models/feed.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/services/bulk_import_service_spec.rb'
- 'spec/services/import_service_spec.rb'
RSpec/AnyInstance:
Exclude:
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/resets_controller_spec.rb'
- 'spec/controllers/admin/settings/branding_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
- 'spec/lib/request_spec.rb'
- 'spec/lib/status_filter_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/validators/follow_limit_validator_spec.rb'
- 'spec/workers/activitypub/delivery_worker_spec.rb'
- 'spec/workers/web/push_notification_worker_spec.rb'
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 22
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, each, example
RSpec/HookArgument:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/well_known/webfinger_controller_spec.rb'
- 'spec/helpers/instance_helper_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/rails_helper.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/import_service_spec.rb'
# Configuration parameters: AssignmentOnly.
RSpec/InstanceVariable:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/auth/confirmations_controller_spec.rb'
- 'spec/controllers/auth/passwords_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
- 'spec/controllers/home_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
- 'spec/models/concerns/account_finder_concern_spec.rb'
- 'spec/models/concerns/account_interactions_spec.rb'
- 'spec/models/public_feed_spec.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/remove_status_service_spec.rb'
- 'spec/services/search_service_spec.rb'
- 'spec/services/unblock_domain_service_spec.rb'
RSpec/LetSetup:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/action_logs_controller_spec.rb'
- 'spec/controllers/admin/instances_controller_spec.rb'
- 'spec/controllers/admin/reports/actions_controller_spec.rb'
- 'spec/controllers/admin/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/filters_controller_spec.rb'
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
@@ -89,6 +177,30 @@ RSpec/LetSetup:
- 'spec/services/unsuspend_account_service_spec.rb'
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
RSpec/MessageChain:
Exclude:
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/helpers/admin/account_moderation_notes_helper_spec.rb'
- 'spec/lib/webfinger_resource_spec.rb'
- 'spec/models/admin/account_action_spec.rb'
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/follow_request_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/fetch_replies_service_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/spec_helper.rb'
- 'spec/validators/status_length_validator_spec.rb'
RSpec/MultipleExpectations:
Max: 8
@@ -105,6 +217,13 @@ Rails/ApplicationController:
Exclude:
- 'app/controllers/health_controller.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity.
Rails/DuplicateAssociation:
Exclude:
- 'app/serializers/activitypub/collection_serializer.rb'
- 'app/serializers/activitypub/note_serializer.rb'
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasAndBelongsToMany:
@@ -128,6 +247,11 @@ Rails/HasManyOrHasOneDependent:
- 'app/models/user.rb'
- 'app/models/web/push_subscription.rb'
Rails/I18nLocaleTexts:
Exclude:
- 'lib/tasks/mastodon.rake'
- 'spec/helpers/flashes_helper_spec.rb'
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
Rails/LexicallyScopedActionFilter:
@@ -214,6 +338,7 @@ Rails/SkipsModelValidations:
- 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
- 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
- 'lib/mastodon/cli/accounts.rb'
- 'lib/mastodon/cli/main.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/lib/activitypub/activity/follow_spec.rb'
- 'spec/services/follow_service_spec.rb'
@@ -306,6 +431,7 @@ Style/FetchEnvVar:
- 'config/initializers/3_omniauth.rb'
- 'config/initializers/blacklists.rb'
- 'config/initializers/cache_buster.rb'
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb'
@@ -503,6 +629,14 @@ Style/SingleArgumentDig:
Exclude:
- 'lib/webpacker/manifest_extensions.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_parentheses, require_no_parentheses
Style/StabbyLambdaParentheses:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/content_security_policy.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
Exclude:
@@ -561,3 +695,5 @@ Style/TrailingCommaInHashLiteral:
Style/WordArray:
Exclude:
- 'app/helpers/languages_helper.rb'
- 'spec/controllers/settings/imports_controller_spec.rb'
- 'spec/models/form/import_spec.rb'

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

@@ -1,3 +0,0 @@
{
"ignore_dirs": ["node_modules/", "public/"]
}

View File

View File

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

View File

@@ -1,22 +0,0 @@
diff --git a/dist/index.js b/dist/index.js
index 57e375592d984e9a429bcd9f800fa2d15cd662e4..0c47d96df3608e23adfd77d887a8f72abbd501c0 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
});
exports.default = void 0;
-var _crypto = _interopRequireDefault(require("crypto"));
+var _createHash = _interopRequireDefault(require("webpack/lib/util/createHash"));
var _path = _interopRequireDefault(require("path"));
@@ -227,7 +227,7 @@ class CompressionPlugin {
originalAlgorithm: this.options.algorithm,
compressionOptions: this.options.compressionOptions,
name,
- contentHash: _crypto.default.createHash("md4").update(input).digest("hex")
+ contentHash: _createHash.default("md4").update(input).digest("hex")
};
} else {
cacheData.name = (0, _serializeJavascript.default)({

49
.yarnclean Normal file
View File

@@ -0,0 +1,49 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
# assets
# examples
example
examples
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
.*.yml
*.yml
# misc
*.gz
*.md
# for specific ignore
!.svgo.yml
!sass-lint/**/*.yml
# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384
!**/yaml/dist/**/doc

View File

@@ -1 +0,0 @@
nodeLinker: node-modules

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.4
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.9-bookworm-slim"
ARG NODE_VERSION="20.8-bookworm-slim"
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
FROM node:${NODE_VERSION} as build
@@ -13,6 +13,7 @@ ENV DEBIAN_FRONTEND="noninteractive" \
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
# hadolint ignore=DL3008
RUN apt-get update && \
@@ -35,15 +36,8 @@ RUN apt-get update && \
bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
corepack enable
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/
COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)"
RUN yarn workspaces focus --all --production && \
bundle install -j"$(nproc)" && \
yarn install --pure-lockfile --production --network-timeout 600000 && \
yarn cache clean
FROM node:${NODE_VERSION}
@@ -84,8 +78,7 @@ RUN apt-get update && \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon && \
corepack enable
ln -s /opt/mastodon /mastodon
# Note: no, cleaning here since Debian does this automatically
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem

21
Gemfile
View File

@@ -9,9 +9,6 @@ gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.7'
# For why irb is in the Gemfile, see: https://ruby.social/@st0012/111444685161478182
gem 'irb', '~> 1.8'
gem 'haml-rails', '~>2.0'
gem 'pg', '~> 1.5'
gem 'pghero'
@@ -19,14 +16,14 @@ gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 1.0', require: false
gem 'fog-openstack', '~> 0.3', require: false
gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.17.0', require: false
gem 'bootsnap', '~> 1.16.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
@@ -91,7 +88,7 @@ gem 'simple-navigation', '~> 4.4'
gem 'simple_form', '~> 5.2'
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
gem 'stoplight', '~> 3.0.1'
gem 'strong_migrations', '1.6.4'
gem 'strong_migrations', '~> 0.8'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2023'
@@ -106,15 +103,15 @@ gem 'rdf-normalize', '~> 0.5'
gem 'private_address_check', '~> 0.5'
group :test do
# Used to split testing into chunks in CI
gem 'rspec_chunked', '~> 0.6'
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
gem 'rspec-github', '~> 2.4', require: false
# RSpec progress bar formatter
gem 'fuubar', '~> 2.5'
# RSpec helpers for email specs
gem 'email_spec'
# Extra RSpec extenion methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0'
@@ -145,7 +142,6 @@ group :test do
# Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-lcov', '~> 0.8', require: false
# Stub web requests for specs
gem 'webmock', '~> 3.18'
@@ -182,9 +178,6 @@ group :development do
end
group :development, :test do
# Interactive Debugging tools
gem 'debug', '~> 1.8'
# Profiling tools
gem 'memory_profiler', require: false
gem 'ruby-prof', require: false
@@ -205,7 +198,7 @@ gem 'xorcist', '~> 1.1'
gem 'cocoon', '~> 1.2'
gem 'net-http', '~> 0.4.0'
gem 'net-http', '~> 0.3.2'
gem 'rubyzip', '~> 2.3'
gem 'hcaptcha', '~> 7.1'

View File

@@ -39,51 +39,50 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
actioncable (7.1.1)
actionpack (= 7.1.1)
activesupport (= 7.1.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
actionmailbox (7.1.1)
actionpack (= 7.1.1)
activejob (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.2)
actionpack (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activesupport (= 7.1.2)
actionmailer (7.1.1)
actionpack (= 7.1.1)
actionview (= 7.1.1)
activejob (= 7.1.1)
activesupport (= 7.1.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.2)
actionview (= 7.1.2)
activesupport (= 7.1.2)
actionpack (7.1.1)
actionview (= 7.1.1)
activesupport (= 7.1.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.2)
actionpack (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
actiontext (7.1.1)
actionpack (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.2)
activesupport (= 7.1.2)
actionview (7.1.1)
activesupport (= 7.1.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@@ -93,22 +92,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.2)
activesupport (= 7.1.2)
activejob (7.1.1)
activesupport (= 7.1.1)
globalid (>= 0.3.6)
activemodel (7.1.2)
activesupport (= 7.1.2)
activerecord (7.1.2)
activemodel (= 7.1.2)
activesupport (= 7.1.2)
activemodel (7.1.1)
activesupport (= 7.1.1)
activerecord (7.1.1)
activemodel (= 7.1.1)
activesupport (= 7.1.1)
timeout (>= 0.4.0)
activestorage (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activesupport (= 7.1.2)
activestorage (7.1.1)
actionpack (= 7.1.1)
activejob (= 7.1.1)
activerecord (= 7.1.1)
activesupport (= 7.1.1)
marcel (~> 1.0)
activesupport (7.1.2)
activesupport (7.1.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -130,21 +129,21 @@ GEM
encryptor (~> 3.0.0)
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.857.0)
aws-sdk-core (3.188.0)
aws-eventstream (1.2.0)
aws-partitions (1.809.0)
aws-sdk-core (3.181.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.73.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.140.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sdk-s3 (1.133.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.7.0)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
@@ -154,8 +153,7 @@ GEM
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8)
base64 (0.2.0)
bcp47_spec (0.2.1)
base64 (0.1.1)
bcrypt (3.1.19)
better_errors (2.10.1)
erubi (>= 1.0.0)
@@ -173,7 +171,7 @@ GEM
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.7)
bootsnap (1.17.0)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (6.0.1)
browser (5.3.1)
@@ -219,10 +217,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
date (3.3.3)
debug_inspector (1.1.0)
devise (4.9.3)
bcrypt (~> 3.0)
@@ -240,18 +235,18 @@ GEM
devise (>= 4.0.0)
rpam2 (~> 4.0)
diff-lcs (1.5.0)
discard (1.3.0)
discard (1.2.1)
activerecord (>= 4.2, < 8)
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.6.7)
doorkeeper (5.6.6)
railties (>= 5)
dotenv (2.8.1)
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
railties (>= 3.2)
drb (2.2.0)
drb (2.1.1)
ruby2_keywords
ed25519 (1.3.0)
elasticsearch (7.13.3)
@@ -263,17 +258,13 @@ GEM
elasticsearch-transport (7.13.3)
faraday (~> 1)
multi_json
email_spec (2.2.2)
htmlentities (~> 4.3.3)
launchy (~> 2.1)
mail (~> 2.7)
encryptor (3.0.0)
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.104.0)
fabrication (2.31.0)
faker (3.2.2)
excon (0.100.0)
fabrication (2.30.0)
faker (3.2.1)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday-em_http (~> 1.0)
@@ -306,18 +297,19 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
fog-core (2.3.0)
fog-core (2.1.0)
builder
excon (~> 0.71)
formatador (>= 0.2, < 2.0)
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-openstack (1.1.0)
fog-core (~> 2.1)
fog-openstack (0.3.10)
fog-core (>= 1.45, <= 2.1.0)
fog-json (>= 1.0)
formatador (1.1.0)
ipaddress (>= 0.8)
formatador (0.3.0)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@@ -377,28 +369,29 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
io-console (0.6.0)
irb (1.9.1)
ipaddress (0.8.3)
irb (1.8.1)
rdoc
reline (>= 0.3.8)
jmespath (1.6.2)
json (2.6.3)
json-canonicalization (1.0.0)
json-canonicalization (0.3.2)
json-jwt (1.15.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
httpclient
json-ld (3.3.1)
json-ld (3.2.5)
htmlentities (~> 4.3)
json-canonicalization (~> 1.0)
json-canonicalization (~> 0.3, >= 0.3.2)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15)
rack (>= 2.2, < 4)
rdf (~> 3.3)
json-ld-preloaded (3.3.0)
json-ld (~> 3.3)
rdf (~> 3.3)
json-schema (4.1.1)
rdf (~> 3.2, >= 3.2.10)
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
json-schema (4.0.0)
addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.7.1)
@@ -439,7 +432,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.22.0)
loofah (2.21.4)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -458,30 +451,30 @@ GEM
memory_profiler (1.0.1)
mime-types (3.5.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.1003)
mime-types-data (3.2023.0808)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
mini_portile2 (2.8.4)
minitest (5.20.0)
msgpack (1.7.2)
msgpack (1.7.1)
multi_json (1.15.0)
multipart-post (2.3.0)
mutex_m (0.2.0)
net-http (0.4.0)
mutex_m (0.1.2)
net-http (0.3.2)
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.4)
net-imap (0.4.1)
date
net-protocol
net-ldap (0.18.0)
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
net-protocol (0.2.1)
timeout
net-smtp (0.4.0)
net-protocol
nio4r (2.5.9)
nokogiri (1.16.2)
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.1)
@@ -521,7 +514,7 @@ GEM
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.5.5)
pg (1.5.4)
pghero (3.3.4)
activerecord (>= 6)
posix-spawn (0.3.15)
@@ -534,15 +527,15 @@ GEM
net-smtp
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
psych (5.1.1.1)
psych (5.1.1)
stringio
public_suffix (5.0.4)
public_suffix (5.0.3)
puma (6.4.0)
nio4r (~> 2.0)
pundit (2.3.1)
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.3)
racc (1.7.1)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
@@ -565,20 +558,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.2)
actioncable (= 7.1.2)
actionmailbox (= 7.1.2)
actionmailer (= 7.1.2)
actionpack (= 7.1.2)
actiontext (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activemodel (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
rails (7.1.1)
actioncable (= 7.1.1)
actionmailbox (= 7.1.1)
actionmailer (= 7.1.1)
actionpack (= 7.1.1)
actiontext (= 7.1.1)
actionview (= 7.1.1)
activejob (= 7.1.1)
activemodel (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
bundler (>= 1.15.0)
railties (= 7.1.2)
railties (= 7.1.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -593,22 +586,21 @@ GEM
rails-i18n (7.0.8)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
railties (7.1.1)
actionpack (= 7.1.1)
activesupport (= 7.1.1)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.1.0)
rdf (3.3.1)
bcp47_spec (~> 0.2)
rake (13.0.6)
rdf (3.2.11)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1)
rdf (~> 3.2)
rdoc (6.6.0)
rdoc (6.5.0)
psych (>= 4.0.0)
redcarpet (3.6.0)
redis (4.8.1)
@@ -617,7 +609,7 @@ GEM
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.8.2)
reline (0.4.0)
reline (0.3.9)
io-console (~> 0.5)
request_store (1.5.1)
rack (>= 1.4)
@@ -639,10 +631,10 @@ GEM
rspec-support (~> 3.12.0)
rspec-github (2.4.0)
rspec-core (~> 3.0)
rspec-mocks (3.12.6)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.1.0)
rspec-rails (6.0.3)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
@@ -650,13 +642,15 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-sidekiq (4.1.0)
rspec-sidekiq (4.0.1)
rspec-core (~> 3.0)
rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8)
rspec-support (3.12.1)
rubocop (1.57.2)
rspec_chunked (0.6)
rubocop (1.57.1)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@@ -667,22 +661,21 @@ GEM
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.19.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.24.0)
rubocop-factory_bot (2.23.1)
rubocop (~> 1.33)
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.22.2)
rubocop-rails (2.20.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rspec (2.25.0)
rubocop (~> 1.40)
rubocop-rspec (2.23.2)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-prof (1.6.3)
@@ -696,13 +689,13 @@ GEM
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (6.1.0)
sanitize (6.0.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
scenic (1.7.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.15.0)
selenium-webdriver (4.13.1)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@@ -717,7 +710,7 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.33)
sidekiq-unique-jobs (7.1.29)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (< 5.0)
@@ -725,7 +718,7 @@ GEM
thor (>= 0.20, < 3.0)
simple-navigation (4.4.0)
activesupport (>= 2.3.2)
simple_form (5.3.0)
simple_form (5.2.0)
actionpack (>= 5.2)
activemodel (>= 5.2)
simplecov (0.22.0)
@@ -733,7 +726,6 @@ GEM
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
sprockets (3.7.2)
@@ -747,8 +739,8 @@ GEM
statsd-ruby (1.5.0)
stoplight (3.0.2)
redlock (~> 1.0)
stringio (3.0.9)
strong_migrations (1.6.4)
stringio (3.0.8)
strong_migrations (0.8.0)
activerecord (>= 5.2)
swd (1.3.0)
activesupport (>= 3)
@@ -760,10 +752,10 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
test-prof (1.3.0)
thor (1.3.0)
test-prof (1.2.3)
thor (1.2.2)
tilt (2.3.0)
timeout (0.4.1)
timeout (0.4.0)
tpm-key_attestation (0.12.0)
bindata (~> 2.4)
openssl (> 2.0)
@@ -841,7 +833,7 @@ DEPENDENCIES
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.17.0)
bootsnap (~> 1.16.0)
brakeman (~> 6.0)
browser
bundler-audit (~> 0.9)
@@ -854,7 +846,6 @@ DEPENDENCIES
concurrent-ruby
connection_pool
database_cleaner-active_record
debug (~> 1.8)
devise (~> 4.9)
devise-two-factor (~> 4.1)
devise_pam_authenticatable2 (~> 9.2)
@@ -862,13 +853,12 @@ DEPENDENCIES
doorkeeper (~> 5.6)
dotenv-rails (~> 2.8)
ed25519 (~> 1.3)
email_spec
fabrication (~> 2.30)
faker (~> 3.2)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.4.0)
fog-openstack (~> 1.0)
fog-openstack (~> 0.3)
fuubar (~> 2.5)
haml-rails (~> 2.0)
haml_lint
@@ -880,7 +870,6 @@ DEPENDENCIES
httplog (~> 1.6.2)
i18n-tasks (~> 1.0)
idn-ruby
irb (~> 1.8)
json-ld
json-ld-preloaded (~> 3.2)
json-schema (~> 4.0)
@@ -894,7 +883,7 @@ DEPENDENCIES
md-paperclip-azure (~> 2.2)
memory_profiler
mime-types (~> 3.5.0)
net-http (~> 0.4.0)
net-http (~> 0.3.2)
net-ldap (~> 0.18)
nokogiri (~> 1.15)
nsa!
@@ -930,6 +919,7 @@ DEPENDENCIES
rspec-github (~> 2.4)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0)
rspec_chunked (~> 0.6)
rubocop
rubocop-capybara
rubocop-performance
@@ -948,12 +938,11 @@ DEPENDENCIES
simple-navigation (~> 4.4)
simple_form (~> 5.2)
simplecov (~> 0.22)
simplecov-lcov (~> 0.8)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.4)
stackprof
stoplight (~> 3.0.1)
strong_migrations (= 1.6.4)
strong_migrations (~> 0.8)
test-prof
thor (~> 1.2)
tty-prompt (~> 0.23)

View File

@@ -1,4 +1,4 @@
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
stream: env PORT=4000 yarn workspace @mastodon/streaming start
stream: env PORT=4000 yarn run start
webpack: bin/webpack-dev-server

View File

@@ -17,6 +17,6 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
| ------- | ---------------- |
| 4.2.x | Yes |
| 4.1.x | Yes |
| 4.0.x | No |
| 4.0.x | Until 2023-10-31 |
| 3.5.x | Until 2023-12-31 |
| < 3.5 | No |

4
Vagrantfile vendored
View File

@@ -112,11 +112,11 @@ bundle install
# Install node modules
sudo corepack enable
corepack prepare
yarn set version classic
yarn install
# Build Mastodon
export RAILS_ENV=development
export RAILS_ENV=development
export $(cat ".env.vagrant" | xargs)
bundle exec rails db:setup

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class AccountsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: {
filter: {
english_stop: {
@@ -62,7 +60,7 @@ class AccountsIndex < Chewy::Index
field(:following_count, type: 'long')
field(:followers_count, type: 'long')
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }

View File

@@ -1,14 +0,0 @@
# frozen_string_literal: true
module DatetimeClampingConcern
extend ActiveSupport::Concern
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
class_methods do
def clamp_date(datetime)
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
end
end
end

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class PublicStatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
@@ -55,7 +53,7 @@ class PublicStatusesIndex < Chewy::Index
index_scope ::Status.unscoped
.kept
.indexable
.includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card)
.includes(:media_attachments, :preloadable_poll, :preview_cards, :tags)
root date_detection: false do
field(:id, type: 'long')
@@ -64,6 +62,6 @@ class PublicStatusesIndex < Chewy::Index
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
field(:created_at, type: 'date')
end
end

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
@@ -52,7 +50,7 @@ class StatusesIndex < Chewy::Index
},
}
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preview_cards_status: :preview_card, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preview_cards, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
root date_detection: false do
field(:id, type: 'long')
@@ -62,6 +60,6 @@ class StatusesIndex < Chewy::Index
field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
field(:created_at, type: 'date')
end
end

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class TagsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: {
analyzer: {
content: {
@@ -44,6 +42,6 @@ class TagsIndex < Chewy::Index
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
field(:last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at })
end
end

View File

@@ -18,6 +18,8 @@ class AccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
@rss_url = rss_url
end
format.rss do
@@ -82,21 +84,29 @@ class AccountsController < ApplicationController
short_account_url(@account, format: 'rss')
end
end
helper_method :rss_url
def media_requested?
path_without_format.end_with?('/media') && !tag_requested?
request.path.split('.').first.end_with?('/media') && !tag_requested?
end
def replies_requested?
path_without_format.end_with?('/with_replies') && !tag_requested?
request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
end
def tag_requested?
path_without_format.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
def path_without_format
request.path.split('.').first
def cached_filtered_status_page
cache_collection_paginated_by_id(
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end
end

View File

@@ -21,7 +21,7 @@ module Admin
account_action.save!
if account_action.with_report?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
else
redirect_to admin_account_path(@account.id)
end

View File

@@ -20,7 +20,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController
authorize @appeal, :approve?
log_action :reject, @appeal
@appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
redirect_to disputes_strike_path(@appeal.strike)
end

View File

@@ -33,7 +33,7 @@ module Admin
# Disallow accidentally downgrading a domain block
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.validate
@domain_block.save
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe
@domain_block.errors.delete(:domain)
return render :new

View File

@@ -31,11 +31,6 @@ module Admin
private
def batched_ordered_status_edits
@status.edits.includes(:account, status: [:account]).find_each(order: :asc)
end
helper_method :batched_ordered_status_edits
def admin_status_batch_action_params
params.require(:admin_status_batch_action).permit(status_ids: [])
end

View File

@@ -7,7 +7,6 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders
include AccessTokenTrackingConcern
include ApiCachingConcern
include Api::ContentSecurityPolicy
skip_before_action :require_functional!, unless: :limited_federation_mode?
@@ -18,6 +17,26 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
content_security_policy do |p|
# Set every directive that does not have a fallback
p.default_src :none
p.frame_ancestors :none
p.form_action :none
# Disable every directive with a fallback to cut on response size
p.base_uri false
p.font_src false
p.img_src false
p.style_src false
p.media_src false
p.frame_src false
p.manifest_src false
p.connect_src false
p.script_src false
p.child_src false
p.worker_src false
end
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422
end

View File

@@ -16,8 +16,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
current_user.update(user_params) if user_params
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
render json: @account, serializer: REST::CredentialAccountSerializer
rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e).as_json, status: 422
end
private

View File

@@ -12,7 +12,7 @@ class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
private
def set_accounts
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections')
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
end
def familiar_followers

View File

@@ -5,8 +5,10 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!
def index
@accounts = Account.where(id: account_ids).select('id')
@accounts.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
accounts = Account.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::AccountsController < Api::BaseController
include RegistrationHelper
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
@@ -92,14 +90,18 @@ class Api::V1::AccountsController < Api::BaseController
end
def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
end
def invite
Invite.find_by(code: params[:invite_code]) if params[:invite_code].present?
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone)
end
def check_enabled_registrations
forbidden unless allowed_registration?(request.remote_ip, invite)
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations?
end
def allowed_registrations?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
end

View File

@@ -41,10 +41,10 @@ class Api::V1::ConversationsController < Api::BaseController
account: :account_stat,
last_status: [
:media_attachments,
:preview_cards,
:status_stat,
:tags,
{
preview_cards_status: :preview_card,
active_mentions: [account: :account_stat],
account: :account_stat,
},

View File

@@ -1,9 +1,11 @@
# frozen_string_literal: true
class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseController
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
WEEKS_OF_ACTIVITY = 12
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
vary_by ''
def show
cache_even_if_authenticated!
@@ -13,40 +15,23 @@ class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseControlle
private
def activity
activity_weeks.map do |weeks_ago|
activity_json(*week_edge_days(weeks_ago))
statuses_tracker = ActivityTracker.new('activity:statuses:local', :basic)
logins_tracker = ActivityTracker.new('activity:logins', :unique)
registrations_tracker = ActivityTracker.new('activity:accounts:local', :basic)
(0...12).map do |i|
start_of_week = i.weeks.ago
end_of_week = start_of_week + 6.days
{
week: start_of_week.to_i.to_s,
statuses: statuses_tracker.sum(start_of_week, end_of_week).to_s,
logins: logins_tracker.sum(start_of_week, end_of_week).to_s,
registrations: registrations_tracker.sum(start_of_week, end_of_week).to_s,
}
end
end
def activity_json(start_of_week, end_of_week)
{
week: start_of_week.to_i.to_s,
statuses: statuses_tracker.sum(start_of_week, end_of_week).to_s,
logins: logins_tracker.sum(start_of_week, end_of_week).to_s,
registrations: registrations_tracker.sum(start_of_week, end_of_week).to_s,
}
end
def activity_weeks
0...WEEKS_OF_ACTIVITY
end
def week_edge_days(num)
[num.weeks.ago, num.weeks.ago + 6.days]
end
def statuses_tracker
ActivityTracker.new('activity:statuses:local', :basic)
end
def logins_tracker
ActivityTracker.new('activity:logins', :unique)
end
def registrations_tracker
ActivityTracker.new('activity:accounts:local', :basic)
end
def require_enabled_api!
head 404 unless Setting.activity_api_enabled && !limited_federation_mode?
end

View File

@@ -1,8 +0,0 @@
# frozen_string_literal: true
class Api::V1::Instances::BaseController < Api::BaseController
skip_before_action :require_authenticated_user!,
unless: :limited_federation_mode?
vary_by ''
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseController
class Api::V1::Instances::DomainBlocksController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
before_action :require_enabled_api!
before_action :set_domain_blocks
@@ -13,40 +15,16 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
cache_if_unauthenticated!
end
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: show_rationale_in_response?
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
end
private
def require_enabled_api!
head 404 unless api_enabled?
end
def api_enabled?
show_domain_blocks_for_all? || show_domain_blocks_to_user?
end
def show_domain_blocks_for_all?
Setting.show_domain_blocks == 'all'
end
def show_domain_blocks_to_user?
Setting.show_domain_blocks == 'users' && user_signed_in?
head 404 unless Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
end
def set_domain_blocks
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
end
def show_rationale_in_response?
always_show_rationale? || show_rationale_for_user?
end
def always_show_rationale?
Setting.show_domain_blocks_rationale == 'all'
end
def show_rationale_for_user?
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
end
end

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::BaseController
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
before_action :set_extended_description
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Api::V1::Instances::LanguagesController < Api::V1::Instances::BaseController
class Api::V1::Instances::LanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
before_action :set_languages
vary_by ''
def show
cache_even_if_authenticated!
render json: @languages, each_serializer: REST::LanguageSerializer

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,8 +1,12 @@
# frozen_string_literal: true
class Api::V1::Instances::PrivacyPoliciesController < Api::V1::Instances::BaseController
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
before_action :set_privacy_policy
vary_by ''
def show
cache_even_if_authenticated!
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
class Api::V1::Instances::RulesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
before_action :set_rules
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,8 +1,12 @@
# frozen_string_literal: true
class Api::V1::Instances::TranslationLanguagesController < Api::V1::Instances::BaseController
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
before_action :set_languages
vary_by ''
def show
cache_even_if_authenticated!
render json: @languages

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
class Api::V1::InvitesController < Api::BaseController
include RegistrationHelper
skip_before_action :require_authenticated_user!
skip_around_action :set_locale
before_action :set_invite
before_action :check_enabled_registrations!
# Override `current_user` to avoid reading session cookies
def current_user; end
def show
render json: { invite_code: params[:invite_code], instance_api_url: api_v2_instance_url }, status: 200
end
private
def set_invite
@invite = Invite.find_by!(code: params[:invite_code])
end
def check_enabled_registrations!
return render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
raise Mastodon::NotPermittedError unless allowed_registration?(request.remote_ip, @invite)
end
end

View File

@@ -1,16 +0,0 @@
# frozen_string_literal: true
class Api::V1::Statuses::BaseController < Api::BaseController
include Authorization
before_action :set_status
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,9 +1,11 @@
# frozen_string_literal: true
class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::BookmarksController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
skip_before_action :set_status, only: [:destroy]
before_action :set_status, only: [:create]
def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
@@ -26,4 +28,13 @@ class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
rescue Mastodon::NotPermittedError
not_found
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,7 +1,10 @@
# frozen_string_literal: true
class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers
def index
@@ -58,6 +61,13 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end

View File

@@ -1,9 +1,11 @@
# frozen_string_literal: true
class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::FavouritesController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
skip_before_action :set_status, only: [:destroy]
before_action :set_status, only: [:create]
def create
FavouriteService.new.call(current_account, @status)
@@ -28,4 +30,13 @@ class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseControlle
rescue Mastodon::NotPermittedError
not_found
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,7 +1,10 @@
# frozen_string_literal: true
class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::HistoriesController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_status
def show
cache_if_unauthenticated!
@@ -11,6 +14,13 @@ class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
private
def status_edits
@status.edits.ordered.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,8 +1,11 @@
# frozen_string_literal: true
class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::MutesController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
before_action :require_user!
before_action :set_status
before_action :set_conversation
def create
@@ -21,6 +24,13 @@ class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_conversation
@conversation = @status.conversation
raise Mastodon::ValidationError if @conversation.nil?

View File

@@ -1,8 +1,11 @@
# frozen_string_literal: true
class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::PinsController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
before_action :require_user!
before_action :set_status
def create
StatusPin.create!(account: current_account, status: @status)
@@ -23,6 +26,10 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
private
def set_status
@status = Status.find(params[:status_id])
end
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,

View File

@@ -1,7 +1,10 @@
# frozen_string_literal: true
class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers
def index
@@ -54,6 +57,13 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end

View File

@@ -1,13 +1,13 @@
# frozen_string_literal: true
class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::ReblogsController < Api::BaseController
include Authorization
include Redisable
include Lockable
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user!
before_action :set_reblog, only: [:create]
skip_before_action :set_status
override_rate_limit_headers :create, family: :statuses

View File

@@ -1,9 +1,21 @@
# frozen_string_literal: true
class Api::V1::Statuses::SourcesController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::SourcesController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
def show
render json: @status, serializer: REST::StatusSourceSerializer
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,7 +1,10 @@
# frozen_string_literal: true
class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController
class Api::V1::Statuses::TranslationsController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
before_action :set_translation
rescue_from TranslationService::NotConfiguredError, with: :not_found
@@ -21,6 +24,13 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseControl
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_translation
@translation = TranslateStatusService.new.call(@status, content_locale)
end

View File

@@ -1,33 +0,0 @@
# frozen_string_literal: true
class Api::V1::Timelines::BaseController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
private
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
def next_path_params
permitted_params.merge(max_id: pagination_max_id)
end
def prev_path_params
permitted_params.merge(min_id: pagination_since_id)
end
def permitted_params
params
.slice(*self.class::PERMITTED_PARAMS)
.permit(*self.class::PERMITTED_PARAMS)
end
end

View File

@@ -1,10 +1,9 @@
# frozen_string_literal: true
class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
class Api::V1::Timelines::HomeController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
before_action :require_user!, only: [:show]
PERMITTED_PARAMS = %i(local limit).freeze
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
with_read_replica do
@@ -41,11 +40,27 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
HomeFeed.new(current_account)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :limit).permit(:local, :limit).merge(core_params)
end
def next_path
api_v1_timelines_home_url next_path_params
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_home_url prev_path_params
api_v1_timelines_home_url pagination_params(min_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -1,12 +1,12 @@
# frozen_string_literal: true
class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
class Api::V1::Timelines::ListController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
before_action :require_user!
before_action :set_list
before_action :set_statuses
PERMITTED_PARAMS = %i(limit).freeze
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
render json: @statuses,
@@ -41,11 +41,27 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
ListFeed.new(@list)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_timelines_list_url params[:id], next_path_params
api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_list_url params[:id], prev_path_params
api_v1_timelines_list_url params[:id], pagination_params(min_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -1,9 +1,8 @@
# frozen_string_literal: true
class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
class Api::V1::Timelines::PublicController < Api::BaseController
before_action :require_user!, only: [:show], if: :require_auth?
PERMITTED_PARAMS = %i(local remote limit only_media allow_local_only).freeze
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
cache_if_unauthenticated!
@@ -46,11 +45,27 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :remote, :limit, :only_media, :allow_local_only).permit(:local, :remote, :limit, :only_media, :allow_local_only).merge(core_params)
end
def next_path
api_v1_timelines_public_url next_path_params
api_v1_timelines_public_url pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_public_url prev_path_params
api_v1_timelines_public_url pagination_params(min_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -1,10 +1,9 @@
# frozen_string_literal: true
class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
class Api::V1::Timelines::TagController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
before_action :load_tag
PERMITTED_PARAMS = %i(local limit only_media).freeze
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
cache_if_unauthenticated!
@@ -52,11 +51,27 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
end
def next_path
api_v1_timelines_tag_url params[:id], next_path_params
api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_tag_url params[:id], prev_path_params
api_v1_timelines_tag_url params[:id], pagination_params(min_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -2,22 +2,12 @@
class Api::V2::MediaController < Api::V1::MediaController
def create
@media_attachment = current_account.media_attachments.create!(media_and_delay_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_from_media_processing
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error => e
Rails.logger.error "#{e.class}: #{e.message}"
render json: processing_error, status: 500
end
private
def media_and_delay_params
{ delay_processing: true }.merge(media_attachment_params)
end
def status_from_media_processing
@media_attachment.not_processed? ? 202 : 200
end
end

View File

@@ -3,13 +3,37 @@
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
before_action :require_user!
before_action :set_push_subscription, only: :update
before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
after_action :update_session_with_subscription, only: :create
def create
@push_subscription = ::Web::PushSubscription.create!(web_push_subscription_params)
active_session = current_session
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
unless active_session.web_push_subscription.nil?
active_session.web_push_subscription.destroy!
active_session.update!(web_push_subscription: nil)
end
# Mobile devices do not support regular notifications, so we enable push notifications by default
alerts_enabled = active_session.detection.device.mobile? || active_session.detection.device.tablet?
data = {
policy: 'all',
alerts: Notification::TYPES.index_with { alerts_enabled },
}
data.deep_merge!(data_params) if params[:data]
push_subscription = ::Web::PushSubscription.create!(
endpoint: subscription_params[:endpoint],
key_p256dh: subscription_params[:keys][:p256dh],
key_auth: subscription_params[:keys][:auth],
data: data,
user_id: active_session.user_id,
access_token_id: active_session.access_token_id
)
active_session.update!(web_push_subscription: push_subscription)
render json: push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def update
@@ -19,41 +43,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
private
def active_session
@active_session ||= current_session
end
def destroy_previous_subscriptions
active_session.web_push_subscription.destroy!
active_session.update!(web_push_subscription: nil)
end
def prior_subscriptions?
active_session.web_push_subscription.present?
end
def subscription_data
default_subscription_data.tap do |data|
data.deep_merge!(data_params) if params[:data]
end
end
def default_subscription_data
{
policy: 'all',
alerts: Notification::TYPES.index_with { alerts_enabled },
}
end
def alerts_enabled
# Mobile devices do not support regular notifications, so we enable push notifications by default
active_session.detection.device.mobile? || active_session.detection.device.tablet?
end
def update_session_with_subscription
active_session.update!(web_push_subscription: @push_subscription)
end
def set_push_subscription
@push_subscription = ::Web::PushSubscription.find(params[:id])
end
@@ -62,17 +51,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
@subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end
def web_push_subscription_params
{
access_token_id: active_session.access_token_id,
data: subscription_data,
endpoint: subscription_params[:endpoint],
key_auth: subscription_params[:keys][:auth],
key_p256dh: subscription_params[:keys][:p256dh],
user_id: active_session.user_id,
}
end
def data_params
@data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
end

View File

@@ -176,10 +176,7 @@ class ApplicationController < ActionController::Base
return unless self_destruct?
respond_to do |format|
format.any do
use_pack 'error'
render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html]
end
format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[410] }, status: code }
end
end

View File

@@ -40,12 +40,6 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
show
end
def redirect_to_app?
truthy_param?(:redirect_to_app)
end
helper_method :redirect_to_app?
private
def require_captcha_if_needed!
@@ -93,7 +87,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
end
def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && redirect_to_app?
if user.created_by_application && truthy_param?(:redirect_to_app)
user.created_by_application.confirmation_redirect_uri
elsif user_signed_in?
web_url('start')

View File

@@ -7,7 +7,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
define_method provider do
@provider = provider
@user = User.find_for_omniauth(request.env['omniauth.auth'], current_user)
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
if @user.persisted?
record_login_activity
@@ -17,9 +17,6 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
rescue ActiveRecord::RecordInvalid
flash[:alert] = I18n.t('devise.failure.omniauth_user_creation_failure') if is_navigational_format?
redirect_to new_user_session_url
end
end

View File

@@ -1,7 +1,6 @@
# frozen_string_literal: true
class Auth::RegistrationsController < Devise::RegistrationsController
include RegistrationHelper
include RegistrationSpamConcern
layout :determine_layout
@@ -84,7 +83,19 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def check_enabled_registrations
redirect_to root_path unless allowed_registration?(request.remote_ip, @invite)
redirect_to root_path if single_user_mode? || omniauth_only? || !allowed_registrations? || ip_blocked?
end
def allowed_registrations?
Setting.registrations_mode != 'none' || @invite&.valid_for_use?
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?
IpBlock.where(severity: :sign_up_block).where('ip >>= ?', request.remote_ip.to_s).exists?
end
def invite_code

View File

@@ -1,27 +0,0 @@
# frozen_string_literal: true
module Api::ContentSecurityPolicy
extend ActiveSupport::Concern
included do
content_security_policy do |policy|
# Set every directive that does not have a fallback
policy.default_src :none
policy.frame_ancestors :none
policy.form_action :none
# Disable every directive with a fallback to cut on response size
policy.base_uri false
policy.font_src false
policy.img_src false
policy.style_src false
policy.media_src false
policy.frame_src false
policy.manifest_src false
policy.connect_src false
policy.script_src false
policy.child_src false
policy.worker_src false
end
end
end

View File

@@ -92,10 +92,18 @@ module CacheConcern
arguments
end
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
if Rails.gem_version >= Gem::Version.new('7.0')
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
else
def attributes_for_database(record)
attributes = record.instance_variable_get(:@attributes).send(:attributes).transform_values(&:value_for_database)
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
end
def deserialize_record(class_name, attributes_from_database, new_record = false) # rubocop:disable Style/OptionalBooleanParameter

View File

@@ -250,7 +250,7 @@ module SignatureVerification
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
account
end
rescue Mastodon::PrivateNetworkAddressError => e

View File

@@ -6,7 +6,7 @@ module Admin::AccountModerationNotesHelper
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
safe_join([
image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'),
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
content_tag(:span, account.acct, class: 'username'),
], ' ')
end

View File

@@ -91,14 +91,6 @@ module ApplicationHelper
end
end
def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
.select(&:present?),
' - '
)
end
def title
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end

View File

@@ -155,8 +155,8 @@ module JsonLdHelper
end
end
def fetch_resource(uri, id_is_known, on_behalf_of = nil)
unless id_is_known
def fetch_resource(uri, id, on_behalf_of = nil)
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
@@ -174,19 +174,7 @@ module JsonLdHelper
build_request(uri, on_behalf_of).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
end
end
def valid_activitypub_content_type?(response)
return true if response.mime_type == 'application/activity+json'
# When the mime type is `application/ld+json`, we need to check the profile,
# but `http.rb` does not parse it for us.
return false unless response.mime_type == 'application/ld+json'
response.headers[HTTP::Headers::CONTENT_TYPE]&.split(';')&.map(&:strip)&.any? do |str|
str.start_with?('profile="') && str[9...-1].split.include?('https://www.w3.org/ns/activitystreams')
body_to_json(response.body_with_limit) if response.code == 200
end
end

View File

@@ -298,3 +298,5 @@ module LanguagesHelper
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end
# rubocop:enable Metrics/ModuleLength

View File

@@ -1,21 +0,0 @@
# frozen_string_literal: true
module RegistrationHelper
extend ActiveSupport::Concern
def allowed_registration?(remote_ip, invite)
!Rails.configuration.x.single_user_mode && !omniauth_only? && (registrations_open? || invite&.valid_for_use?) && !ip_blocked?(remote_ip)
end
def registrations_open?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?(remote_ip)
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
end
end

View File

@@ -25,7 +25,7 @@ module SettingsHelper
return if account.nil?
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
end
end
end

View File

@@ -0,0 +1,69 @@
import api from '../api';
export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
export const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
export const ACCOUNT_NOTE_INIT_EDIT = 'ACCOUNT_NOTE_INIT_EDIT';
export const ACCOUNT_NOTE_CANCEL = 'ACCOUNT_NOTE_CANCEL';
export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
export function submitAccountNote() {
return (dispatch, getState) => {
dispatch(submitAccountNoteRequest());
const id = getState().getIn(['account_notes', 'edit', 'account_id']);
api(getState).post(`/api/v1/accounts/${id}/note`, {
comment: getState().getIn(['account_notes', 'edit', 'comment']),
}).then(response => {
dispatch(submitAccountNoteSuccess(response.data));
}).catch(error => dispatch(submitAccountNoteFail(error)));
};
}
export function submitAccountNoteRequest() {
return {
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
};
}
export function submitAccountNoteSuccess(relationship) {
return {
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
relationship,
};
}
export function submitAccountNoteFail(error) {
return {
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
};
}
export function initEditAccountNote(account) {
return (dispatch, getState) => {
const comment = getState().getIn(['relationships', account.get('id'), 'note']);
dispatch({
type: ACCOUNT_NOTE_INIT_EDIT,
account,
comment,
});
};
}
export function cancelAccountNote() {
return {
type: ACCOUNT_NOTE_CANCEL,
};
}
export function changeAccountNoteComment(comment) {
return {
type: ACCOUNT_NOTE_CHANGE_COMMENT,
comment,
};
}

View File

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

View File

@@ -1,15 +1,5 @@
import api, { getLinks } from '../api';
import {
followAccountSuccess, unfollowAccountSuccess,
authorizeFollowRequestSuccess, rejectFollowRequestSuccess,
followAccountRequest, followAccountFail,
unfollowAccountRequest, unfollowAccountFail,
muteAccountSuccess, unmuteAccountSuccess,
blockAccountSuccess, unblockAccountSuccess,
pinAccountSuccess, unpinAccountSuccess,
fetchRelationshipsSuccess,
} from './accounts_typed';
import { importFetchedAccount, importFetchedAccounts } from './importer';
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
@@ -20,22 +10,36 @@ export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';
export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST';
export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS';
export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL';
export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL';
export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';
export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL';
export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST';
export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS';
export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL';
export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
@@ -55,6 +59,7 @@ export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS';
export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL';
export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST';
export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS';
export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL';
export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST';
@@ -86,9 +91,8 @@ export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE = 'PINNED_ACCOUNTS_EDITOR
export const PINNED_ACCOUNTS_EDITOR_RESET = 'PINNED_ACCOUNTS_EDITOR_RESET';
export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
export * from './accounts_typed';
export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
export function fetchAccount(id) {
return (dispatch, getState) => {
@@ -102,6 +106,7 @@ export function fetchAccount(id) {
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
dispatch(importFetchedAccount(response.data));
}).then(() => {
dispatch(fetchAccountSuccess());
}).catch(error => {
dispatch(fetchAccountFail(id, error));
@@ -164,12 +169,12 @@ export function followAccount(id, options = { reblogs: true }) {
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
const locked = getState().getIn(['accounts', id, 'locked'], false);
dispatch(followAccountRequest({ id, locked }));
dispatch(followAccountRequest(id, locked));
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing}));
dispatch(followAccountSuccess(response.data, alreadyFollowing));
}).catch(error => {
dispatch(followAccountFail({ id, error, locked }));
dispatch(followAccountFail(error, locked));
});
};
}
@@ -179,22 +184,74 @@ export function unfollowAccount(id) {
dispatch(unfollowAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')}));
dispatch(unfollowAccountSuccess(response.data, getState().get('statuses')));
}).catch(error => {
dispatch(unfollowAccountFail({ id, error }));
dispatch(unfollowAccountFail(error));
});
};
}
export function followAccountRequest(id, locked) {
return {
type: ACCOUNT_FOLLOW_REQUEST,
id,
locked,
skipLoading: true,
};
}
export function followAccountSuccess(relationship, alreadyFollowing) {
return {
type: ACCOUNT_FOLLOW_SUCCESS,
relationship,
alreadyFollowing,
skipLoading: true,
};
}
export function followAccountFail(error, locked) {
return {
type: ACCOUNT_FOLLOW_FAIL,
error,
locked,
skipLoading: true,
};
}
export function unfollowAccountRequest(id) {
return {
type: ACCOUNT_UNFOLLOW_REQUEST,
id,
skipLoading: true,
};
}
export function unfollowAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_UNFOLLOW_SUCCESS,
relationship,
statuses,
skipLoading: true,
};
}
export function unfollowAccountFail(error) {
return {
type: ACCOUNT_UNFOLLOW_FAIL,
error,
skipLoading: true,
};
}
export function blockAccount(id) {
return (dispatch, getState) => {
dispatch(blockAccountRequest(id));
api(getState).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
dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
dispatch(blockAccountSuccess(response.data, getState().get('statuses')));
}).catch(error => {
dispatch(blockAccountFail({ id, error }));
dispatch(blockAccountFail(id, error));
});
};
}
@@ -204,9 +261,9 @@ export function unblockAccount(id) {
dispatch(unblockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => {
dispatch(unblockAccountSuccess({ relationship: response.data }));
dispatch(unblockAccountSuccess(response.data));
}).catch(error => {
dispatch(unblockAccountFail({ id, error }));
dispatch(unblockAccountFail(id, error));
});
};
}
@@ -217,6 +274,15 @@ export function blockAccountRequest(id) {
id,
};
}
export function blockAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_BLOCK_SUCCESS,
relationship,
statuses,
};
}
export function blockAccountFail(error) {
return {
type: ACCOUNT_BLOCK_FAIL,
@@ -231,6 +297,13 @@ export function unblockAccountRequest(id) {
};
}
export function unblockAccountSuccess(relationship) {
return {
type: ACCOUNT_UNBLOCK_SUCCESS,
relationship,
};
}
export function unblockAccountFail(error) {
return {
type: ACCOUNT_UNBLOCK_FAIL,
@@ -245,9 +318,9 @@ export function muteAccount(id, notifications, duration=0) {
api(getState).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
dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
}).catch(error => {
dispatch(muteAccountFail({ id, error }));
dispatch(muteAccountFail(id, error));
});
};
}
@@ -257,9 +330,9 @@ export function unmuteAccount(id) {
dispatch(unmuteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
dispatch(unmuteAccountSuccess({ relationship: response.data }));
dispatch(unmuteAccountSuccess(response.data));
}).catch(error => {
dispatch(unmuteAccountFail({ id, error }));
dispatch(unmuteAccountFail(id, error));
});
};
}
@@ -271,6 +344,14 @@ export function muteAccountRequest(id) {
};
}
export function muteAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_MUTE_SUCCESS,
relationship,
statuses,
};
}
export function muteAccountFail(error) {
return {
type: ACCOUNT_MUTE_FAIL,
@@ -285,6 +366,13 @@ export function unmuteAccountRequest(id) {
};
}
export function unmuteAccountSuccess(relationship) {
return {
type: ACCOUNT_UNMUTE_SUCCESS,
relationship,
};
}
export function unmuteAccountFail(error) {
return {
type: ACCOUNT_UNMUTE_FAIL,
@@ -480,8 +568,8 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsRequest(newAccountIds));
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess(response.data));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));
});
@@ -496,6 +584,14 @@ export function fetchRelationshipsRequest(ids) {
};
}
export function fetchRelationshipsSuccess(relationships) {
return {
type: RELATIONSHIPS_FETCH_SUCCESS,
relationships,
skipLoading: true,
};
}
export function fetchRelationshipsFail(error) {
return {
type: RELATIONSHIPS_FETCH_FAIL,
@@ -583,7 +679,7 @@ export function authorizeFollowRequest(id) {
api(getState)
.post(`/api/v1/follow_requests/${id}/authorize`)
.then(() => dispatch(authorizeFollowRequestSuccess({ id })))
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
};
}
@@ -595,6 +691,13 @@ export function authorizeFollowRequestRequest(id) {
};
}
export function authorizeFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
id,
};
}
export function authorizeFollowRequestFail(id, error) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
@@ -610,7 +713,7 @@ export function rejectFollowRequest(id) {
api(getState)
.post(`/api/v1/follow_requests/${id}/reject`)
.then(() => dispatch(rejectFollowRequestSuccess({ id })))
.then(() => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
};
}
@@ -622,6 +725,13 @@ export function rejectFollowRequestRequest(id) {
};
}
export function rejectFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_REJECT_SUCCESS,
id,
};
}
export function rejectFollowRequestFail(id, error) {
return {
type: FOLLOW_REQUEST_REJECT_FAIL,
@@ -635,7 +745,7 @@ export function pinAccount(id) {
dispatch(pinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess({ relationship: response.data }));
dispatch(pinAccountSuccess(response.data));
}).catch(error => {
dispatch(pinAccountFail(error));
});
@@ -647,7 +757,7 @@ export function unpinAccount(id) {
dispatch(unpinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess({ relationship: response.data }));
dispatch(unpinAccountSuccess(response.data));
}).catch(error => {
dispatch(unpinAccountFail(error));
});
@@ -661,6 +771,13 @@ export function pinAccountRequest(id) {
};
}
export function pinAccountSuccess(relationship) {
return {
type: ACCOUNT_PIN_SUCCESS,
relationship,
};
}
export function pinAccountFail(error) {
return {
type: ACCOUNT_PIN_FAIL,
@@ -675,6 +792,13 @@ export function unpinAccountRequest(id) {
};
}
export function unpinAccountSuccess(relationship) {
return {
type: ACCOUNT_UNPIN_SUCCESS,
relationship,
};
}
export function unpinAccountFail(error) {
return {
type: ACCOUNT_UNPIN_FAIL,
@@ -682,6 +806,11 @@ export function unpinAccountFail(error) {
};
}
export const revealAccount = id => ({
type: ACCOUNT_REVEAL,
id,
});
export function fetchPinnedAccounts() {
return (dispatch, getState) => {
dispatch(fetchPinnedAccountsRequest());

View File

@@ -1,97 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
export const revealAccount = createAction<{
id: string;
}>('accounts/revealAccount');
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
'accounts/importAccounts',
);
function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
return {
payload: {
...args,
skipLoading: true,
},
};
}
export const followAccountSuccess = createAction(
'accounts/followAccount/SUCCESS',
actionWithSkipLoadingTrue<{
relationship: ApiRelationshipJSON;
alreadyFollowing: boolean;
}>,
);
export const unfollowAccountSuccess = createAction(
'accounts/unfollowAccount/SUCCESS',
actionWithSkipLoadingTrue<{
relationship: ApiRelationshipJSON;
statuses: unknown;
alreadyFollowing?: boolean;
}>,
);
export const authorizeFollowRequestSuccess = createAction<{ id: string }>(
'accounts/followRequestAuthorize/SUCCESS',
);
export const rejectFollowRequestSuccess = createAction<{ id: string }>(
'accounts/followRequestReject/SUCCESS',
);
export const followAccountRequest = createAction(
'accounts/follow/REQUEST',
actionWithSkipLoadingTrue<{ id: string; locked: boolean }>,
);
export const followAccountFail = createAction(
'accounts/follow/FAIL',
actionWithSkipLoadingTrue<{ id: string; error: string; locked: boolean }>,
);
export const unfollowAccountRequest = createAction(
'accounts/unfollow/REQUEST',
actionWithSkipLoadingTrue<{ id: string }>,
);
export const unfollowAccountFail = createAction(
'accounts/unfollow/FAIL',
actionWithSkipLoadingTrue<{ id: string; error: string }>,
);
export const blockAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
statuses: unknown;
}>('accounts/block/SUCCESS');
export const unblockAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unblock/SUCCESS');
export const muteAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
statuses: unknown;
}>('accounts/mute/SUCCESS');
export const unmuteAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unmute/SUCCESS');
export const pinAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/pin/SUCCESS');
export const unpinAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unpin/SUCCESS');
export const fetchRelationshipsSuccess = createAction(
'relationships/fetch/SUCCESS',
actionWithSkipLoadingTrue<{ relationships: ApiRelationshipJSON[] }>,
);

View File

@@ -12,48 +12,52 @@ export const ALERT_DISMISS = 'ALERT_DISMISS';
export const ALERT_CLEAR = 'ALERT_CLEAR';
export const ALERT_NOOP = 'ALERT_NOOP';
export const dismissAlert = alert => ({
type: ALERT_DISMISS,
alert,
});
export function dismissAlert(alert) {
return {
type: ALERT_DISMISS,
alert,
};
}
export const clearAlert = () => ({
type: ALERT_CLEAR,
});
export function clearAlert() {
return {
type: ALERT_CLEAR,
};
}
export const showAlert = alert => ({
type: ALERT_SHOW,
alert,
});
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
return {
type: ALERT_SHOW,
title,
message,
message_values,
};
}
export const showAlertForError = (error, skipNotFound = false) => {
export function showAlertForError(error, skipNotFound = false) {
if (error.response) {
const { data, status, statusText, headers } = error.response;
// Skip these errors as they are reflected in the UI
if (skipNotFound && (status === 404 || status === 410)) {
// Skip these errors as they are reflected in the UI
return { type: ALERT_NOOP };
}
// Rate limit errors
if (status === 429 && headers['x-ratelimit-reset']) {
return showAlert({
title: messages.rateLimitedTitle,
message: messages.rateLimitedMessage,
values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
});
const reset_date = new Date(headers['x-ratelimit-reset']);
return showAlert(messages.rateLimitedTitle, messages.rateLimitedMessage, { 'retry_time': reset_date });
}
return showAlert({
title: `${status}`,
message: data.error || statusText,
});
let message = statusText;
let title = `${status}`;
if (data.error) {
message = data.error;
}
return showAlert(title, message);
} else {
console.error(error);
return showAlert();
}
console.error(error);
return showAlert({
title: messages.unexpectedTitle,
message: messages.unexpectedMessage,
});
};
}

View File

@@ -84,14 +84,10 @@ export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTIO
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
open: { id: 'compose.published.open', defaultMessage: 'Open' },
published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
});
export const ensureComposeIsVisible = (getState, routerHistory) => {
@@ -148,15 +144,6 @@ export function resetCompose() {
};
}
export const focusCompose = (routerHistory, defaultText) => dispatch => {
dispatch({
type: COMPOSE_FOCUS,
defaultText,
});
ensureComposeIsVisible(routerHistory);
};
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
@@ -251,9 +238,9 @@ export function submitCompose(routerHistory) {
return;
}
// To make the app more responsive, immediately push the status
// into the columns
const insertIfOnline = timelineId => {
// To make the app more responsive, immediately get the status into the columns
const insertIfOnline = (timelineId) => {
const timeline = getState().getIn(['timelines', timelineId]);
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
@@ -277,13 +264,6 @@ export function submitCompose(routerHistory) {
} else if (statusId === null && response.data.visibility === 'direct') {
insertIfOnline('direct');
}
dispatch(showAlert({
message: statusId === null ? messages.published : messages.saved,
action: messages.open,
dismissAfter: 10000,
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
}));
}).catch(function (error) {
dispatch(submitComposeFail(error));
});
@@ -320,19 +300,18 @@ export function doodleSet(options) {
export function uploadCompose(files) {
return function (dispatch, getState) {
const uploadLimit = 4;
const media = getState().getIn(['compose', 'media_attachments']);
const pending = getState().getIn(['compose', 'pending_media_attachments']);
const media = getState().getIn(['compose', 'media_attachments']);
const pending = getState().getIn(['compose', 'pending_media_attachments']);
const progress = new Array(files.length).fill(0);
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
if (files.length + media.size + pending > uploadLimit) {
dispatch(showAlert({ message: messages.uploadErrorLimit }));
dispatch(showAlert(undefined, messages.uploadErrorLimit));
return;
}
if (getState().getIn(['compose', 'poll'])) {
dispatch(showAlert({ message: messages.uploadErrorPoll }));
dispatch(showAlert(undefined, messages.uploadErrorPoll));
return;
}
@@ -662,9 +641,8 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
return (dispatch, getState) => {
let completion;
if (suggestion.type === 'emoji') {
completion = suggestion.native || suggestion.colons;
dispatch(useEmoji(suggestion));
completion = suggestion.native || suggestion.colons;
} else if (suggestion.type === 'hashtag') {
completion = `#${suggestion.name}`;
} else if (suggestion.type === 'account') {

View File

@@ -1,13 +1,11 @@
import api, { getLinks } from '../api';
import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed";
export * from "./domain_blocks_typed";
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
@@ -26,7 +24,7 @@ export function blockDomain(domain) {
const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
dispatch(blockDomainSuccess({ domain, accounts }));
dispatch(blockDomainSuccess(domain, accounts));
}).catch(err => {
dispatch(blockDomainFail(domain, err));
});
@@ -40,6 +38,14 @@ export function blockDomainRequest(domain) {
};
}
export function blockDomainSuccess(domain, accounts) {
return {
type: DOMAIN_BLOCK_SUCCESS,
domain,
accounts,
};
}
export function blockDomainFail(domain, error) {
return {
type: DOMAIN_BLOCK_FAIL,
@@ -55,7 +61,7 @@ export function unblockDomain(domain) {
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
const at_domain = '@' + domain;
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));
}).catch(err => {
dispatch(unblockDomainFail(domain, err));
});
@@ -69,6 +75,14 @@ export function unblockDomainRequest(domain) {
};
}
export function unblockDomainSuccess(domain, accounts) {
return {
type: DOMAIN_UNBLOCK_SUCCESS,
domain,
accounts,
};
}
export function unblockDomainFail(domain, error) {
return {
type: DOMAIN_UNBLOCK_FAIL,

View File

@@ -1,13 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import type { Account } from 'flavours/glitch/models/account';
export const blockDomainSuccess = createAction<{
domain: string;
accounts: Account[];
}>('domain_blocks/block/SUCCESS');
export const unblockDomainSuccess = createAction<{
domain: string;
accounts: Account[];
}>('domain_blocks/unblock/SUCCESS');

View File

@@ -0,0 +1,31 @@
import api from '../api';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL = 'IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL';
export const fetchAccountIdentityProofs = accountId => (dispatch, getState) => {
dispatch(fetchAccountIdentityProofsRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/identity_proofs`)
.then(({ data }) => dispatch(fetchAccountIdentityProofsSuccess(accountId, data)))
.catch(err => dispatch(fetchAccountIdentityProofsFail(accountId, err)));
};
export const fetchAccountIdentityProofsRequest = id => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
id,
});
export const fetchAccountIdentityProofsSuccess = (accountId, identity_proofs) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
accountId,
identity_proofs,
});
export const fetchAccountIdentityProofsFail = (accountId, err) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
accountId,
err,
skipNotFound: true,
});

View File

@@ -1,7 +1,7 @@
import { importAccounts } from '../accounts_typed';
import { normalizeStatus, normalizePoll } from './normalizer';
import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer';
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
export const STATUS_IMPORT = 'STATUS_IMPORT';
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
export const POLLS_IMPORT = 'POLLS_IMPORT';
@@ -13,6 +13,14 @@ function pushUnique(array, object) {
}
}
export function importAccount(account) {
return { type: ACCOUNT_IMPORT, account };
}
export function importAccounts(accounts) {
return { type: ACCOUNTS_IMPORT, accounts };
}
export function importStatus(status) {
return { type: STATUS_IMPORT, status };
}
@@ -37,7 +45,7 @@ export function importFetchedAccounts(accounts) {
const normalAccounts = [];
function processAccount(account) {
pushUnique(normalAccounts, account);
pushUnique(normalAccounts, normalizeAccount(account));
if (account.moved) {
processAccount(account.moved);
@@ -46,7 +54,7 @@ export function importFetchedAccounts(accounts) {
accounts.forEach(processAccount);
return importAccounts({ accounts: normalAccounts });
return importAccounts(normalAccounts);
}
export function importFetchedStatus(status) {

View File

@@ -1,7 +1,8 @@
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../../features/emoji/emoji';
import { autoHideCW } from '../../utils/content_warning';
import emojify from 'flavours/glitch/features/emoji/emoji';
import { autoHideCW } from 'flavours/glitch/utils/content_warning';
import { unescapeHTML } from 'flavours/glitch/utils/html';
const domParser = new DOMParser();
@@ -16,6 +17,32 @@ export function searchTextFromRawStatus (status) {
return domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
}
export function normalizeAccount(account) {
account = { ...account };
const emojiMap = makeEmojiMap(account.emojis);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.note_emojified = emojify(account.note, emojiMap);
account.note_plain = unescapeHTML(account.note);
if (account.fields) {
account.fields = account.fields.map(pair => ({
...pair,
name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value),
}));
}
if (account.moved) {
account.moved = account.moved.id;
}
return account;
}
export function normalizeFilterResult(result) {
const normalResult = { ...result };

View File

@@ -83,7 +83,6 @@ export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -91,7 +90,6 @@ export function reblogSuccess(status) {
return {
type: REBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -100,7 +98,6 @@ export function reblogFail(status, error) {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -108,7 +105,6 @@ export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -116,7 +112,6 @@ export function unreblogSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -125,7 +120,6 @@ export function unreblogFail(status, error) {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -159,7 +153,6 @@ export function favouriteRequest(status) {
return {
type: FAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -167,7 +160,6 @@ export function favouriteSuccess(status) {
return {
type: FAVOURITE_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -176,7 +168,6 @@ export function favouriteFail(status, error) {
type: FAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -184,7 +175,6 @@ export function unfavouriteRequest(status) {
return {
type: UNFAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -192,7 +182,6 @@ export function unfavouriteSuccess(status) {
return {
type: UNFAVOURITE_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -201,7 +190,6 @@ export function unfavouriteFail(status, error) {
type: UNFAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -211,7 +199,7 @@ export function bookmark(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status, response.data));
dispatch(bookmarkSuccess(status));
}).catch(function (error) {
dispatch(bookmarkFail(status, error));
});
@@ -224,7 +212,7 @@ export function unbookmark(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status, response.data));
dispatch(unbookmarkSuccess(status));
}).catch(error => {
dispatch(unbookmarkFail(status, error));
});
@@ -238,11 +226,10 @@ export function bookmarkRequest(status) {
};
}
export function bookmarkSuccess(status, response) {
export function bookmarkSuccess(status) {
return {
type: BOOKMARK_SUCCESS,
status: status,
response: response,
};
}
@@ -261,11 +248,10 @@ export function unbookmarkRequest(status) {
};
}
export function unbookmarkSuccess(status, response) {
export function unbookmarkSuccess(status) {
return {
type: UNBOOKMARK_SUCCESS,
status: status,
response: response,
};
}
@@ -458,7 +444,6 @@ export function pinRequest(status) {
return {
type: PIN_REQUEST,
status,
skipLoading: true,
};
}
@@ -466,7 +451,6 @@ export function pinSuccess(status) {
return {
type: PIN_SUCCESS,
status,
skipLoading: true,
};
}
@@ -475,7 +459,6 @@ export function pinFail(status, error) {
type: PIN_FAIL,
status,
error,
skipLoading: true,
};
}
@@ -496,7 +479,6 @@ export function unpinRequest(status) {
return {
type: UNPIN_REQUEST,
status,
skipLoading: true,
};
}
@@ -504,7 +486,6 @@ export function unpinSuccess(status) {
return {
type: UNPIN_SUCCESS,
status,
skipLoading: true,
};
}
@@ -513,6 +494,5 @@ export function unpinFail(status, error) {
type: UNPIN_FAIL,
status,
error,
skipLoading: true,
};
}

View File

@@ -1,8 +1,9 @@
import { openModal } from 'flavours/glitch/actions/modal';
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';

View File

@@ -5,10 +5,10 @@ import { List as ImmutableList } from 'immutable';
import { compareId } from 'flavours/glitch/compare_id';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state';
import { unescapeHTML } from 'flavours/glitch/utils/html';
import { requestNotificationPermission } from 'flavours/glitch/utils/notifications';
import api, { getLinks } from '../api';
import { unescapeHTML } from '../utils/html';
import { requestNotificationPermission } from '../utils/notifications';
import { fetchFollowRequests, fetchRelationships } from './accounts';
import {
@@ -18,12 +18,13 @@ import {
importFetchedStatuses,
} from './importer';
import { submitMarkers } from './markers';
import { notificationsUpdate } from "./notifications_typed";
import { register as registerPushNotifications } from './push_notifications';
import { saveSettings } from './settings';
export * from "./notifications_typed";
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
// tracking the notif cleaning request
@@ -64,7 +65,7 @@ defineMessages({
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
if (accountIds.length > 0) {
if (accountIds > 0) {
dispatch(fetchRelationships(accountIds));
}
};
@@ -109,8 +110,12 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
dispatch(importFetchedAccount(notification.report.target_account));
}
dispatch(notificationsUpdate({ notification, preferPendingItems, playSound: playSound && !filtered}));
dispatch({
type: NOTIFICATIONS_UPDATE,
notification,
usePendingItems: preferPendingItems,
meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
});
fetchRelatedRelationships(dispatch, [notification]);
} else if (playSound && !filtered) {
@@ -126,7 +131,6 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
notify.addEventListener('click', () => {
window.focus();
notify.close();
@@ -137,6 +141,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
const excludeTypesFromFilter = filter => {
const allTypes = ImmutableList([
'follow',

View File

@@ -1,23 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import type { ApiAccountJSON } from '../api_types/accounts';
// To be replaced once ApiNotificationJSON type exists
interface FakeApiNotificationJSON {
type: string;
account: ApiAccountJSON;
}
export const notificationsUpdate = createAction(
'notifications/update',
({
playSound,
...args
}: {
notification: FakeApiNotificationJSON;
usePendingItems: boolean;
playSound: boolean;
}) => ({
payload: args,
meta: { sound: playSound ? 'boop' : undefined },
}),
);

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