Compare commits

..

20 commits

Author SHA1 Message Date
KMY(雪あすか)
b7c7b2afb2
Merge pull request #986 from kmycode/kb-draft-17.1
Release: 17.1
2025-02-28 17:59:42 +09:00
KMY
3cba3a6af3 Fix regexp 2025-02-28 16:51:34 +09:00
KMY
ac26f5f48a Fix system css 2025-02-28 15:18:11 +09:00
KMY
a4c43dcf18 Fix: NGワード設定画面のエラー 2025-02-28 15:16:55 +09:00
KMY
d5ba371a5e Fix test 2025-02-28 13:08:04 +09:00
KMY
d1208a2cf5 Bump version to 17.1 2025-02-28 09:54:45 +09:00
Claire
8f25192072 Change HTML sanitization to remove unusable and unused embed tag (#34021) 2025-02-28 09:45:42 +09:00
Jeremy Kescher
55e31110e3 Merge commit from fork
* Fix domain blocks/rationales being visible to unapproved/unconfirmed users

* Fix domain blocks/rationales being visible to suspended users

Co-authored-by: Claire <claire.github-309c@sitedethib.com>

* Allow moved users to view domain blocks

* Add authorization specs for `/api/v1/instance/domain_blocks` spec

* Fix tests

* Fix incorrect test setup

---------

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2025-02-28 09:44:55 +09:00
Claire
e09adc6314 Merge commit from fork
* Add rate-limit on `/auth/setup`

* Remove useless test
2025-02-28 09:44:45 +09:00
Claire
6515244a16 Fix GET /api/v2/notifications/:id and POST /api/v2/notifications/:id/dismiss for ungrouped notifications (#33990) 2025-02-28 09:43:03 +09:00
renovate[bot]
9ed1cb3c29 chore(deps): update dependency nokogiri to v1.18.3 [security] (#33961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 09:42:31 +09:00
renovate[bot]
7afe02b36b chore(deps): update dependency rack to v2.2.11 (#33900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 09:42:06 +09:00
Claire
4da06664a6 Update dependency net-imap to 0.5.6 (#33901) 2025-02-28 09:40:56 +09:00
renovate[bot]
689d431dc6 chore(deps): update dependency ruby-vips to v2.2.3 (#33853)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 09:40:27 +09:00
Claire
4fa550d881 Fix handling of duplicate mentions in incoming status Update (#33911) 2025-02-28 09:39:46 +09:00
Claire
d4f0b01207 Fix filtering for lists (#33842) 2025-02-28 09:38:32 +09:00
Claire
5fb4ae8edf Optimize timeline generation (#33839) 2025-02-28 09:36:41 +09:00
Claire
e97f8d1b59 Fix emoji rewrite adding unnecessary curft to the DOM for most emoji (#33818) 2025-02-28 09:28:56 +09:00
KMY(雪あすか)
25d18d0bc8
Merge pull request #979 from kmycode/kb-draft-17.0
Release: 17.0
2025-02-12 19:11:24 +09:00
KMY
fdca24ba56 Bump version to 17.0 2025-02-10 12:44:42 +09:00
960 changed files with 14661 additions and 23963 deletions

View file

@ -21,13 +21,12 @@ services:
ES_HOST: es ES_HOST: es
ES_PORT: '9200' ES_PORT: '9200'
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000 LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
LOCAL_DOMAIN: ${LOCAL_DOMAIN:-localhost:3000}
# Overrides default command so things don't shut down after the process ends. # Overrides default command so things don't shut down after the process ends.
command: sleep infinity command: sleep infinity
ports: ports:
- '3000:3000' - '127.0.0.1:3000:3000'
- '3035:3035' - '127.0.0.1:3035:3035'
- '4000:4000' - '127.0.0.1:4000:4000'
networks: networks:
- external_network - external_network
- internal_network - internal_network

View file

@ -20,9 +20,3 @@ postgres14
redis redis
elasticsearch elasticsearch
chart chart
.yarn/
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

View file

@ -79,9 +79,6 @@ AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY= AWS_SECRET_ACCESS_KEY=
S3_ALIAS_HOST=files.example.com S3_ALIAS_HOST=files.example.com
# Optional list of hosts that are allowed to serve media for your instance
# EXTRA_MEDIA_HOSTS=https://data.example1.com,https://data.example2.com
# IP and session retention # IP and session retention
# ----------------------- # -----------------------
# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml # Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
@ -89,27 +86,3 @@ S3_ALIAS_HOST=files.example.com
# ----------------------- # -----------------------
IP_RETENTION_PERIOD=31556952 IP_RETENTION_PERIOD=31556952
SESSION_RETENTION_PERIOD=31556952 SESSION_RETENTION_PERIOD=31556952
# Fetch All Replies Behavior
# --------------------------
# When a user expands a post (DetailedStatus view), fetch all of its replies
# (default: false)
FETCH_REPLIES_ENABLED=false
# Period to wait between fetching replies (in minutes)
FETCH_REPLIES_COOLDOWN_MINUTES=15
# Period to wait after a post is first created before fetching its replies (in minutes)
FETCH_REPLIES_INITIAL_WAIT_MINUTES=5
# Max number of replies to fetch - total, recursively through a whole reply tree
FETCH_REPLIES_MAX_GLOBAL=1000
# Max number of replies to fetch - for a single post
FETCH_REPLIES_MAX_SINGLE=500
# Max number of replies Collection pages to fetch - total
FETCH_REPLIES_MAX_PAGES=500
# Maximum allowed character count
MAX_CHARS=5555

13
.eslintignore Normal file
View file

@ -0,0 +1,13 @@
/build/**
/coverage/**
/db/**
/lib/**
/log/**
/node_modules/**
/nonobox/**
/public/**
!/public/embed.js
/spec/**
/tmp/**
/vendor/**
!.eslintrc.js

367
.eslintrc.js Normal file
View file

@ -0,0 +1,367 @@
// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:promise/recommended',
'plugin:jsdoc/recommended',
],
env: {
browser: true,
node: true,
es6: true,
},
parser: '@typescript-eslint/parser',
plugins: [
'react',
'jsx-a11y',
'import',
'promise',
'@typescript-eslint',
'formatjs',
],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2021,
requireConfigFile: false,
babelOptions: {
configFile: false,
presets: ['@babel/react', '@babel/env'],
},
},
settings: {
react: {
version: 'detect',
},
'import/ignore': [
'node_modules',
'\\.(css|scss|json)$',
],
'import/resolver': {
typescript: {},
},
},
rules: {
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
'indent': ['error', 2],
'jsx-quotes': ['error', 'prefer-single'],
'semi': ['error', 'always'],
'no-catch-shadow': 'error',
'no-console': [
'warn',
{
allow: [
'error',
'warn',
],
},
],
'no-empty': ['error', { "allowEmptyCatch": true }],
'no-restricted-properties': [
'error',
{ property: 'substring', message: 'Use .slice instead of .substring.' },
{ property: 'substr', message: 'Use .slice instead of .substr.' },
],
'no-restricted-syntax': [
'error',
{
// eslint-disable-next-line no-restricted-syntax
selector: 'Literal[value=/•/], JSXText[value=/•/]',
// eslint-disable-next-line no-restricted-syntax
message: "Use '·' (middle dot) instead of '•' (bullet)",
},
],
'no-unused-expressions': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'valid-typeof': 'error',
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
'react/jsx-boolean-value': 'error',
'react/display-name': 'off',
'react/jsx-fragments': ['error', 'syntax'],
'react/jsx-equals-spacing': 'error',
'react/jsx-no-bind': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
'react/jsx-tag-spacing': 'error',
'react/jsx-uses-react': 'off', // not needed with new JSX transform
'react/jsx-wrap-multilines': 'error',
'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
'react/self-closing-comp': 'error',
// recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.8.0/src/index.js#L46
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-autofocus': 'off',
// recommended rule is:
// 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
// 'error',
// {
// tr: ['none', 'presentation'],
// canvas: ['img'],
// },
// ],
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
// recommended rule is:
// 'jsx-a11y/no-noninteractive-tabindex': [
// 'error',
// {
// tags: [],
// roles: ['tabpanel'],
// allowExpressionValues: true,
// },
// ],
'jsx-a11y/no-noninteractive-tabindex': 'off',
// recommended is full 'error'
'jsx-a11y/no-static-element-interactions': [
'warn',
{
handlers: [
'onClick',
],
},
],
// See https://github.com/import-js/eslint-plugin-import/blob/v2.29.1/config/recommended.js
'import/extensions': [
'error',
'always',
{
js: 'never',
jsx: 'never',
mjs: 'never',
ts: 'never',
tsx: 'never',
},
],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'.eslintrc.js',
'config/webpack/**',
'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js',
'app/javascript/**/__tests__/**',
],
},
],
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-relative-packages': 'error',
'import/no-self-import': 'error',
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/order': [
'error',
{
alphabetize: { order: 'asc' },
'newlines-between': 'always',
groups: [
'builtin',
'external',
'internal',
'parent',
['index', 'sibling'],
'object',
],
pathGroups: [
// React core packages
{
pattern: '{react,react-dom,react-dom/client,prop-types}',
group: 'builtin',
position: 'after',
},
// I18n
{
pattern: '{react-intl,intl-messageformat}',
group: 'builtin',
position: 'after',
},
// Common React utilities
{
pattern: '{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
// Immutable / Redux / data store
{
pattern: '{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
group: 'external',
position: 'before',
},
// Internal packages
{
pattern: '{mastodon/**}',
group: 'internal',
position: 'after',
},
],
pathGroupsExcludedImportTypes: [],
},
],
'promise/always-return': 'off',
'promise/catch-or-return': [
'error',
{
allowFinally: true,
},
],
'promise/no-callback-in-promise': 'off',
'promise/no-nesting': 'off',
'promise/no-promise-in-callback': 'off',
'formatjs/blocklist-elements': 'error',
'formatjs/enforce-default-message': ['error', 'literal'],
'formatjs/enforce-description': 'off', // description values not currently used
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
'formatjs/enforce-plural-rules': 'error',
'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming
'formatjs/no-complex-selectors': 'error',
'formatjs/no-emoji': 'error',
'formatjs/no-id': 'off', // IDs are used for translation keys
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-whitespaces': 'error',
'formatjs/no-offset': 'error',
'formatjs/no-useless-message': 'error',
'formatjs/prefer-formatted-message': 'error',
'formatjs/prefer-pound-in-plural': 'error',
'jsdoc/check-types': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
},
overrides: [
{
files: [
'.eslintrc.js',
'*.config.js',
'.*rc.js',
'ide-helper.js',
'config/webpack/**/*',
'config/formatjs-formatter.js',
],
env: {
commonjs: true,
},
parserOptions: {
sourceType: 'script',
},
rules: {
'import/no-commonjs': 'off',
},
},
{
files: [
'**/*.ts',
'**/*.tsx',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/strict-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:jsdoc/recommended-typescript',
],
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
rules: {
// Disable formatting rules that have been enabled in the base config
'indent': 'off',
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
'consistent-return': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
"@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
"@typescript-eslint/no-restricted-imports": [
"warn",
{
"name": "react-redux",
"importNames": ["useSelector", "useDispatch"],
"message": "Use typed hooks `useAppDispatch` and `useAppSelector` instead."
}
],
"@typescript-eslint/restrict-template-expressions": ['warn', { allowNumber: true }],
'jsdoc/require-jsdoc': 'off',
// Those rules set stricter rules for TS files
// to enforce better practices when converting from JS
'import/no-default-export': 'warn',
'react/prefer-stateless-function': 'warn',
'react/function-component-definition': ['error', { namedComponents: 'arrow-function' }],
'react/jsx-uses-react': 'off', // not needed with new JSX transform
'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
'react/prop-types': 'off',
},
},
{
files: [
'**/__tests__/*.js',
'**/__tests__/*.jsx',
],
env: {
jest: true,
},
}
],
});

View file

@ -15,8 +15,6 @@
// to `null` after any other rule set it to something. // 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).', 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'], postUpdateOptions: ['yarnDedupeHighest'],
// The types are now included in recent versions,we ignore them here until we upgrade and remove the dependency
ignoreDeps: ['@types/emoji-mart'],
packageRules: [ packageRules: [
{ {
// Require Dependency Dashboard Approval for major version bumps of these node packages // Require Dependency Dashboard Approval for major version bumps of these node packages
@ -99,13 +97,7 @@
{ {
// Group all eslint-related packages with `eslint` in the same PR // Group all eslint-related packages with `eslint` in the same PR
matchManagers: ['npm'], matchManagers: ['npm'],
matchPackageNames: [ matchPackageNames: ['eslint', 'eslint-*', '@typescript-eslint/*'],
'eslint',
'eslint-*',
'typescript-eslint',
'@eslint/*',
'globals',
],
matchUpdateTypes: ['patch', 'minor'], matchUpdateTypes: ['patch', 'minor'],
groupName: 'eslint (non-major)', groupName: 'eslint (non-major)',
}, },

View file

@ -24,6 +24,8 @@ jobs:
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: Dockerfile file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
cache: false cache: false
push_to_images: | push_to_images: |
tootsuite/mastodon tootsuite/mastodon
@ -44,6 +46,8 @@ jobs:
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: streaming/Dockerfile file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
cache: false cache: false
push_to_images: | push_to_images: |
tootsuite/mastodon-streaming tootsuite/mastodon-streaming

View file

@ -46,4 +46,4 @@ jobs:
- name: Run haml-lint - name: Run haml-lint
run: | run: |
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json" echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
bin/haml-lint --reporter github bin/haml-lint --parallel --reporter github

View file

@ -14,7 +14,7 @@ on:
- 'tsconfig.json' - 'tsconfig.json'
- '.nvmrc' - '.nvmrc'
- '.prettier*' - '.prettier*'
- 'eslint.config.mjs' - '.eslint*'
- '**/*.js' - '**/*.js'
- '**/*.jsx' - '**/*.jsx'
- '**/*.ts' - '**/*.ts'
@ -28,7 +28,7 @@ on:
- 'tsconfig.json' - 'tsconfig.json'
- '.nvmrc' - '.nvmrc'
- '.prettier*' - '.prettier*'
- 'eslint.config.mjs' - '.eslint*'
- '**/*.js' - '**/*.js'
- '**/*.jsx' - '**/*.jsx'
- '**/*.ts' - '**/*.ts'
@ -47,7 +47,7 @@ jobs:
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript
- name: ESLint - name: ESLint
run: yarn workspaces foreach --all --parallel run lint:js --max-warnings 0 run: yarn lint:js --max-warnings 0
- name: Typecheck - name: Typecheck
run: yarn typecheck run: yarn typecheck

View file

@ -67,6 +67,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
DISABLE_SIMPLECOV: true
RAILS_ENV: test RAILS_ENV: test
BUNDLE_CLEAN: true BUNDLE_CLEAN: true
BUNDLE_FROZEN: true BUNDLE_FROZEN: true
@ -80,18 +81,6 @@ jobs:
- name: Set up Ruby environment - name: Set up Ruby environment
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
- name: Ensure no errors with `db:prepare`
run: |
bin/rails db:drop
bin/rails db:prepare
bin/rails db:migrate
- name: Ensure no errors with `db:prepare` and SKIP_POST_DEPLOYMENT_MIGRATIONS
run: |
bin/rails db:drop
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:prepare
bin/rails db:migrate
- name: Test "one step migration" flow - name: Test "one step migration" flow
run: | run: |
bin/rails db:drop bin/rails db:drop

View file

@ -110,7 +110,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }} DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
RAILS_ENV: test RAILS_ENV: test
ALLOW_NOPAM: true ALLOW_NOPAM: true
PAM_ENABLED: true PAM_ENABLED: true
@ -212,7 +212,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }} DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
RAILS_ENV: test RAILS_ENV: test
ALLOW_NOPAM: true ALLOW_NOPAM: true
PAM_ENABLED: true PAM_ENABLED: true
@ -299,6 +299,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
DISABLE_SIMPLECOV: true
RAILS_ENV: test RAILS_ENV: test
BUNDLE_WITH: test BUNDLE_WITH: test
ES_ENABLED: false ES_ENABLED: false
@ -415,6 +416,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
DISABLE_SIMPLECOV: true
RAILS_ENV: test RAILS_ENV: test
BUNDLE_WITH: test BUNDLE_WITH: test
ES_ENABLED: true ES_ENABLED: true

2
.nvmrc
View file

@ -1 +1 @@
22.14 22.13

View file

@ -63,7 +63,6 @@ docker-compose.override.yml
# Ignore emoji map file # Ignore emoji map file
/app/javascript/mastodon/features/emoji/emoji_map.json /app/javascript/mastodon/features/emoji/emoji_map.json
/app/javascript/mastodon/features/emoji/emoji_sheet.json
# Ignore locale files # Ignore locale files
/app/javascript/mastodon/locales/*.json /app/javascript/mastodon/locales/*.json

View file

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

View file

@ -18,7 +18,6 @@ inherit_from:
- .rubocop/rspec_rails.yml - .rubocop/rspec_rails.yml
- .rubocop/rspec.yml - .rubocop/rspec.yml
- .rubocop/style.yml - .rubocop/style.yml
- .rubocop/i18n.yml
- .rubocop/custom.yml - .rubocop/custom.yml
- .rubocop_todo.yml - .rubocop_todo.yml
- .rubocop/strict.yml - .rubocop/strict.yml
@ -27,10 +26,10 @@ inherit_mode:
merge: merge:
- Exclude - Exclude
plugins: require:
- rubocop-capybara
- rubocop-i18n
- rubocop-performance
- rubocop-rails - rubocop-rails
- rubocop-rspec - rubocop-rspec
- rubocop-rspec_rails - rubocop-rspec_rails
- rubocop-performance
- rubocop-capybara
- ./lib/linter/rubocop_middle_dot

View file

@ -1,12 +0,0 @@
I18n/RailsI18n:
Enabled: true
Exclude:
- 'config/**/*'
- 'db/**/*'
- 'lib/**/*'
- 'spec/**/*'
I18n/GetText:
Enabled: false
I18n/RailsI18n/DecorateStringFormattingUsingInterpolation:
Enabled: false

View file

@ -2,9 +2,6 @@
Rails/BulkChangeTable: Rails/BulkChangeTable:
Enabled: false # Conflicts with strong_migrations features Enabled: false # Conflicts with strong_migrations features
Rails/Delegate:
Enabled: false
Rails/FilePath: Rails/FilePath:
EnforcedStyle: arguments EnforcedStyle: arguments

View file

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.75.2. # using RuboCop version 1.70.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -49,7 +49,7 @@ Style/FetchEnvVar:
- 'lib/tasks/repo.rake' - 'lib/tasks/repo.rake'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns. # Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns.
# SupportedStyles: annotated, template, unannotated # SupportedStyles: annotated, template, unannotated
# AllowedMethods: redirect # AllowedMethods: redirect
Style/FormatStringToken: Style/FormatStringToken:
@ -62,10 +62,22 @@ Style/FormatStringToken:
Style/GuardClause: Style/GuardClause:
Enabled: false Enabled: false
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/HashTransformValues:
Exclude:
- 'app/serializers/rest/web_push_subscription_serializer.rb'
- 'app/services/import_service.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/MapToHash:
Exclude:
- 'app/models/status.rb'
# Configuration parameters: AllowedMethods. # Configuration parameters: AllowedMethods.
# AllowedMethods: respond_to_missing? # AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter: Style/OptionalBooleanParameter:
Exclude: Exclude:
- 'app/helpers/json_ld_helper.rb'
- 'app/lib/admin/system_check/message.rb' - 'app/lib/admin/system_check/message.rb'
- 'app/lib/request.rb' - 'app/lib/request.rb'
- 'app/lib/webfinger.rb' - 'app/lib/webfinger.rb'

View file

@ -1 +1 @@
3.4.3 3.4.1

View file

@ -2,88 +2,6 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [4.3.7] - 2025-04-02
### Add
- Add delay to profile updates to debounce them (#34137 by @ClearlyClaire)
- Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire)
### Changed
- Change account suspensions to be federated to recently-followed accounts as well (#34294 by @ClearlyClaire)
- Change `AccountReachFinder` to consider statuses based on suspension date (#32805 and #34291 by @ClearlyClaire and @mjankowski)
- Change user archive signed URL TTL from 10 seconds to 1 hour (#34254 by @ClearlyClaire)
### Fixed
- Fix static version of animated PNG emojis not being properly extracted (#34337 by @ClearlyClaire)
- Fix filters not applying in detailed view, favourites and bookmarks (#34259 and #34260 by @ClearlyClaire)
- Fix handling of malformed/unusual HTML (#34201 by @ClearlyClaire)
- Fix `CacheBuster` being queued for missing media attachments (#34253 by @ClearlyClaire)
- Fix incorrect URL being used when cache busting (#34189 by @ClearlyClaire)
- Fix streaming server refusing unix socket path in `DATABASE_URL` (#34091 by @ClearlyClaire)
- Fix “x” hotkey not working on boosted filtered posts (#33758 by @ClearlyClaire)
## [4.3.6] - 2025-03-13
### Security
- Update dependency `omniauth-saml`
- Update dependency `rack`
### Fixed
- Fix Stoplight errors when using `REDIS_NAMESPACE` (#34126 by @ClearlyClaire)
## [4.3.5] - 2025-03-10
### Changed
- Change hashtag suggestion to prefer personal history capitalization (#34070 by @ClearlyClaire)
### Fixed
- Fix processing errors for some HEIF images from iOS 18 (#34086 by @renchap)
- Fix streaming server not filtering unknown-language posts from public timelines (#33774 by @ClearlyClaire)
- Fix preview cards under Content Warnings not being shown in detailed statuses (#34068 by @ClearlyClaire)
- Fix username and display name being hidden on narrow screens in moderation interface (#33064 by @ClearlyClaire)
## [4.3.4] - 2025-02-27
### Security
- Update dependencies
- Change HTML sanitization to remove unusable and unused `embed` tag (#34021 by @ClearlyClaire, [GHSA-mq2m-hr29-8gqf](https://github.com/mastodon/mastodon/security/advisories/GHSA-mq2m-hr29-8gqf))
- Fix rate-limit on sign-up email verification ([GHSA-v39f-c9jj-8w7h](https://github.com/mastodon/mastodon/security/advisories/GHSA-v39f-c9jj-8w7h))
- Fix improper disclosure of domain blocks to unverified users ([GHSA-94h4-fj37-c825](https://github.com/mastodon/mastodon/security/advisories/GHSA-94h4-fj37-c825))
### Changed
- Change preview cards to be shown when Content Warnings are expanded (#33827 by @ClearlyClaire)
- Change warnings against changing encryption secrets to be even more noticeable (#33631 by @ClearlyClaire)
- Change `mastodon:setup` to prevent overwriting already-configured servers (#33603, #33616, and #33684 by @ClearlyClaire and @mjankowski)
- Change notifications from moderators to not be filtered (#32974 and #33654 by @ClearlyClaire and @mjankowski)
### Fixed
- Fix `GET /api/v2/notifications/:id` and `POST /api/v2/notifications/:id/dismiss` for ungrouped notifications (#33990 by @ClearlyClaire)
- Fix issue with some versions of libvips on some systems (#33853 by @kleisauke)
- Fix handling of duplicate mentions in incoming status `Update` (#33911 by @ClearlyClaire)
- Fix inefficiencies in timeline generation (#33839 and #33842 by @ClearlyClaire)
- Fix emoji rewrite adding unnecessary curft to the DOM for most emoji (#33818 by @ClearlyClaire)
- Fix `tootctl feeds build` not building list timelines (#33783 by @ClearlyClaire)
- Fix flaky test in `/api/v2/notifications` tests (#33773 by @ClearlyClaire)
- Fix incorrect signature after HTTP redirect (#33757 and #33769 by @ClearlyClaire)
- Fix polls not being validated on edition (#33755 by @ClearlyClaire)
- Fix media preview height in compose form when 3 or more images are attached (#33571 by @ClearlyClaire)
- Fix preview card sizing in “Author attribution” in profile settings (#33482 by @ClearlyClaire)
- Fix processing of incoming notifications for unfilterable types (#33429 by @ClearlyClaire)
- Fix featured tags for remote accounts not being kept up to date (#33372, #33406, and #33425 by @ClearlyClaire and @mjankowski)
- Fix notification polling showing a loading bar in web UI (#32960 by @Gargron)
- Fix accounts table long display name (#29316 by @WebCoder49)
- Fix exclusive lists interfering with notifications (#28162 by @ShadowJonathan)
## [4.3.3] - 2025-01-16 ## [4.3.3] - 2025-01-16
### Security ### Security

View file

@ -13,13 +13,13 @@ ARG BASE_REGISTRY="docker.io"
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
# renovate: datasource=docker depName=docker.io/ruby # renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.4.2" ARG RUBY_VERSION="3.4.1"
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
# renovate: datasource=node-version depName=node # renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="22" ARG NODE_MAJOR_VERSION="22"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm" ARG DEBIAN_VERSION="bookworm"
# Node.js image to use for base image based on combined variables (ex: 20-bookworm-slim) # Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm) # Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
@ -61,7 +61,7 @@ ENV \
ENV \ ENV \
# Configure the IP to bind Mastodon to when serving traffic # Configure the IP to bind Mastodon to when serving traffic
BIND="0.0.0.0" \ BIND="0.0.0.0" \
# Use production settings for Yarn, Node.js and related tools # Use production settings for Yarn, Node and related nodejs based tools
NODE_ENV="production" \ NODE_ENV="production" \
# Use production settings for Ruby on Rails # Use production settings for Ruby on Rails
RAILS_ENV="production" \ RAILS_ENV="production" \
@ -96,9 +96,6 @@ RUN \
# Set /opt/mastodon as working directory # Set /opt/mastodon as working directory
WORKDIR /opt/mastodon WORKDIR /opt/mastodon
# Add backport repository for some specific packages where we need the latest version
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
# hadolint ignore=DL3008,DL3005 # hadolint ignore=DL3008,DL3005
RUN \ RUN \
# Mount Apt cache and lib directories from Docker buildx caches # Mount Apt cache and lib directories from Docker buildx caches
@ -128,6 +125,13 @@ RUN \
# Create temporary build layer from base image # Create temporary build layer from base image
FROM ruby AS build FROM ruby AS build
# Copy Node package configuration files into working directory
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY .yarn /opt/mastodon/.yarn
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib /usr/local/lib
ARG TARGETPLATFORM ARG TARGETPLATFORM
# hadolint ignore=DL3008 # hadolint ignore=DL3008
@ -161,7 +165,7 @@ RUN \
libexif-dev \ libexif-dev \
libexpat1-dev \ libexpat1-dev \
libgirepository1.0-dev \ libgirepository1.0-dev \
libheif-dev/bookworm-backports \ libheif-dev \
libimagequant-dev \ libimagequant-dev \
libjpeg62-turbo-dev \ libjpeg62-turbo-dev \
liblcms2-dev \ liblcms2-dev \
@ -181,12 +185,18 @@ RUN \
libx265-dev \ libx265-dev \
; ;
RUN \
# Configure Corepack
rm /usr/local/bin/yarn*; \
corepack enable; \
corepack prepare --activate;
# Create temporary libvips specific build layer from build layer # Create temporary libvips specific build layer from build layer
FROM build AS libvips FROM build AS libvips
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"] # libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips # renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
ARG VIPS_VERSION=8.16.1 ARG VIPS_VERSION=8.16.0
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"] # libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
@ -271,37 +281,38 @@ RUN \
# Download and install required Gems # Download and install required Gems
bundle install -j"$(nproc)"; bundle install -j"$(nproc)";
# Create temporary assets build layer from build layer # Create temporary node specific build layer from build layer
FROM build AS precompiler FROM build AS yarn
ARG TARGETPLATFORM ARG TARGETPLATFORM
# Copy Mastodon sources into layer # Copy Node package configuration files into working directory
COPY . /opt/mastodon/ COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/
# Copy Node.js binaries/libraries into layer COPY .yarn /opt/mastodon/.yarn
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib /usr/local/lib
RUN \
# Configure Corepack
rm /usr/local/bin/yarn*; \
corepack enable; \
corepack prepare --activate;
# hadolint ignore=DL3008 # hadolint ignore=DL3008
RUN \ RUN \
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
# Install Node.js packages # Install Node packages
yarn workspaces focus --production @mastodon/mastodon; yarn workspaces focus --production @mastodon/mastodon;
# Copy libvips components into layer for precompiler # Create temporary assets build layer from build layer
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin FROM build AS precompiler
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
# Copy bundler packages into layer for precompiler # Copy Mastodon sources into precompiler layer
COPY . /opt/mastodon/
# Copy bundler and node packages from build layer to container
COPY --from=yarn /opt/mastodon /opt/mastodon/
COPY --from=bundler /opt/mastodon /opt/mastodon/ COPY --from=bundler /opt/mastodon /opt/mastodon/
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/ COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
# Copy libvips components to layer for precompiler
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
ARG TARGETPLATFORM
RUN \ RUN \
ldconfig; \ ldconfig; \
@ -337,7 +348,7 @@ RUN \
# libvips components # libvips components
libcgif0 \ libcgif0 \
libexif12 \ libexif12 \
libheif1/bookworm-backports \ libheif1 \
libimagequant0 \ libimagequant0 \
libjpeg62-turbo \ libjpeg62-turbo \
liblcms2-2 \ liblcms2-2 \

22
Gemfile
View file

@ -14,7 +14,6 @@ gem 'haml-rails', '~>2.0'
gem 'pg', '~> 1.5' gem 'pg', '~> 1.5'
gem 'pghero' gem 'pghero'
gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873
gem 'aws-sdk-s3', '~> 1.123', require: false gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'blurhash', '~> 0.1' gem 'blurhash', '~> 0.1'
gem 'fog-core', '<= 2.6.0' gem 'fog-core', '<= 2.6.0'
@ -40,7 +39,7 @@ gem 'net-ldap', '~> 0.18'
gem 'omniauth', '~> 2.0' gem 'omniauth', '~> 2.0'
gem 'omniauth-cas', '~> 3.0.0.beta.1' gem 'omniauth-cas', '~> 3.0.0.beta.1'
gem 'omniauth_openid_connect', '~> 0.8.0' gem 'omniauth_openid_connect', '~> 0.6.1'
gem 'omniauth-rails_csrf_protection', '~> 1.0' gem 'omniauth-rails_csrf_protection', '~> 1.0'
gem 'omniauth-saml', '~> 2.0' gem 'omniauth-saml', '~> 2.0'
@ -62,7 +61,6 @@ gem 'inline_svg'
gem 'irb', '~> 1.8' gem 'irb', '~> 1.8'
gem 'kaminari', '~> 1.2' gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0' gem 'link_header', '~> 0.0'
gem 'linzer', '~> 0.6.1'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar' gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
gem 'mutex_m' gem 'mutex_m'
@ -104,10 +102,10 @@ gem 'rdf-normalize', '~> 0.5'
gem 'prometheus_exporter', '~> 2.2', require: false gem 'prometheus_exporter', '~> 2.2', require: false
gem 'opentelemetry-api', '~> 1.5.0' gem 'opentelemetry-api', '~> 1.4.0'
group :opentelemetry do group :opentelemetry do
gem 'opentelemetry-exporter-otlp', '~> 0.30.0', require: false gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false
gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false
@ -118,7 +116,7 @@ group :opentelemetry do
gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false
gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false
gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false
gem 'opentelemetry-instrumentation-rails', '~> 0.36.0', require: false gem 'opentelemetry-instrumentation-rails', '~> 0.35.0', require: false
gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false
gem 'opentelemetry-sdk', '~> 1.4', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false
@ -147,6 +145,9 @@ group :test do
# Used to mock environment variables # Used to mock environment variables
gem 'climate_control' gem 'climate_control'
# Add back helpers functions removed in Rails 5.1
gem 'rails-controller-testing', '~> 1.0'
# Validate schemas in specs # Validate schemas in specs
gem 'json-schema', '~> 5.0' gem 'json-schema', '~> 5.0'
@ -155,7 +156,7 @@ group :test do
gem 'shoulda-matchers' gem 'shoulda-matchers'
# Coverage formatter for RSpec # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
gem 'simplecov', '~> 0.22', require: false gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-lcov', '~> 0.8', require: false gem 'simplecov-lcov', '~> 0.8', require: false
@ -167,14 +168,13 @@ group :development do
# Code linting CLI and plugins # Code linting CLI and plugins
gem 'rubocop', require: false gem 'rubocop', require: false
gem 'rubocop-capybara', require: false gem 'rubocop-capybara', require: false
gem 'rubocop-i18n', require: false
gem 'rubocop-performance', require: false gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false gem 'rubocop-rspec', require: false
gem 'rubocop-rspec_rails', require: false gem 'rubocop-rspec_rails', require: false
# Annotates modules with schema # Annotates modules with schema
gem 'annotaterb', '~> 4.13', require: false gem 'annotaterb', '~> 4.13'
# Enhanced error message pages for development # Enhanced error message pages for development
gem 'better_errors', '~> 2.9' gem 'better_errors', '~> 2.9'
@ -197,7 +197,7 @@ end
group :development, :test do group :development, :test do
# Interactive Debugging tools # Interactive Debugging tools
gem 'debug', '~> 1.8', require: false gem 'debug', '~> 1.8'
# Generate fake data values # Generate fake data values
gem 'faker', '~> 3.2' gem 'faker', '~> 3.2'
@ -209,7 +209,7 @@ group :development, :test do
gem 'memory_profiler', require: false gem 'memory_profiler', require: false
gem 'ruby-prof', require: false gem 'ruby-prof', require: false
gem 'stackprof', require: false gem 'stackprof', require: false
gem 'test-prof', require: false gem 'test-prof'
# RSpec runner for rails # RSpec runner for rails
gem 'rspec-rails', '~> 7.0' gem 'rspec-rails', '~> 7.0'

View file

@ -10,29 +10,29 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.2) actioncable (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.2) actionmailbox (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.2) actionmailer (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.2) actionpack (8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@ -40,15 +40,15 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.0.2) actiontext (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.2) actionview (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -58,22 +58,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (8.0.2) activejob (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.2) activemodel (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
activerecord (8.0.2) activerecord (8.0.1)
activemodel (= 8.0.2) activemodel (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.2) activestorage (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.2) activesupport (8.0.1)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
bigdecimal bigdecimal
@ -90,12 +90,12 @@ GEM
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0) aes_key_wrap (1.1.0)
android_key_attestation (0.3.0) android_key_attestation (0.3.0)
annotaterb (4.14.0) annotaterb (4.13.0)
ast (2.4.3) ast (2.4.2)
attr_required (1.0.2) attr_required (1.0.2)
aws-eventstream (1.3.2) aws-eventstream (1.3.0)
aws-partitions (1.1087.0) aws-partitions (1.1032.0)
aws-sdk-core (3.215.1) aws-sdk-core (3.214.1)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
@ -107,9 +107,9 @@ GEM
aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0) aws-sigv4 (1.10.1)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
azure-blob (0.5.7) azure-blob (0.5.4)
rexml rexml
base64 (0.2.0) base64 (0.2.0)
bcp47_spec (0.2.1) bcp47_spec (0.2.1)
@ -120,13 +120,13 @@ GEM
rack (>= 0.9.0) rack (>= 0.9.0)
rouge (>= 1.0.0) rouge (>= 1.0.0)
bigdecimal (3.1.9) bigdecimal (3.1.9)
bindata (2.5.1) bindata (2.5.0)
binding_of_caller (1.0.1) binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0) debug_inspector (>= 1.2.0)
blurhash (0.1.8) blurhash (0.1.8)
bootsnap (1.18.4) bootsnap (1.18.4)
msgpack (~> 1.2) msgpack (~> 1.2)
brakeman (7.0.2) brakeman (7.0.0)
racc racc
browser (6.2.0) browser (6.2.0)
brpoplpush-redis_script (0.1.3) brpoplpush-redis_script (0.1.3)
@ -168,9 +168,9 @@ GEM
bigdecimal bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
css_parser (1.21.1) css_parser (1.21.0)
addressable addressable
csv (3.3.4) csv (3.3.2)
database_cleaner-active_record (2.2.0) database_cleaner-active_record (2.2.0)
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
@ -194,14 +194,14 @@ GEM
devise_pam_authenticatable2 (9.2.0) devise_pam_authenticatable2 (9.2.0)
devise (>= 4.0.0) devise (>= 4.0.0)
rpam2 (~> 4.0) rpam2 (~> 4.0)
diff-lcs (1.6.1) diff-lcs (1.5.1)
discard (1.4.0) discard (1.4.0)
activerecord (>= 4.2, < 9.0) activerecord (>= 4.2, < 9.0)
docile (1.4.1) docile (1.4.1)
domain_name (0.6.20240107) domain_name (0.6.20240107)
doorkeeper (5.8.2) doorkeeper (5.8.1)
railties (>= 5) railties (>= 5)
dotenv (3.1.8) dotenv (3.1.7)
drb (2.2.1) drb (2.2.1)
elasticsearch (7.17.11) elasticsearch (7.17.11)
elasticsearch-api (= 7.17.11) elasticsearch-api (= 7.17.11)
@ -217,29 +217,24 @@ GEM
htmlentities (~> 4.3.3) htmlentities (~> 4.3.3)
launchy (>= 2.1, < 4.0) launchy (>= 2.1, < 4.0)
mail (~> 2.7) mail (~> 2.7)
email_validator (2.2.4)
activemodel
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.2.11) et-orbi (1.2.11)
tzinfo tzinfo
excon (1.2.5) excon (0.112.0)
logger
fabrication (2.31.0) fabrication (2.31.0)
faker (3.5.1) faker (3.5.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (2.13.0) faraday (2.12.2)
faraday-net_http (>= 2.0, < 3.5) faraday-net_http (>= 2.0, < 3.5)
json json
logger logger
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-httpclient (2.0.1) faraday-httpclient (2.0.1)
httpclient (>= 2.2) httpclient (>= 2.2)
faraday-net_http (3.4.0) faraday-net_http (3.4.0)
net-http (>= 0.5.0) net-http (>= 0.5.0)
fast_blank (1.0.1) fast_blank (1.0.1)
fastimage (2.4.0) fastimage (2.4.0)
ffi (1.17.2) ffi (1.17.1)
ffi-compiler (1.3.2) ffi-compiler (1.3.2)
ffi (>= 1.15.5) ffi (>= 1.15.5)
rake rake
@ -249,15 +244,15 @@ GEM
flatware-rspec (2.3.4) flatware-rspec (2.3.4)
flatware (= 2.3.4) flatware (= 2.3.4)
rspec (>= 3.6) rspec (>= 3.6)
fog-core (2.6.0) fog-core (2.5.0)
builder builder
excon (~> 1.0) excon (~> 0.71)
formatador (>= 0.2, < 2.0) formatador (>= 0.2, < 2.0)
mime-types mime-types
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
fog-openstack (1.1.5) fog-openstack (1.1.3)
fog-core (~> 2.1) fog-core (~> 2.1)
fog-json (>= 1.0) fog-json (>= 1.0)
formatador (1.1.0) formatador (1.1.0)
@ -266,10 +261,8 @@ GEM
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
google-protobuf (4.30.2) google-protobuf (3.25.5)
bigdecimal googleapis-common-protos-types (1.15.0)
rake (>= 13)
googleapis-common-protos-types (1.19.0)
google-protobuf (>= 3.18, < 5.a) google-protobuf (>= 3.18, < 5.a)
haml (6.3.0) haml (6.3.0)
temple (>= 0.8.2) temple (>= 0.8.2)
@ -280,7 +273,7 @@ GEM
activesupport (>= 5.1) activesupport (>= 5.1)
haml (>= 4.0.6) haml (>= 4.0.6)
railties (>= 5.1) railties (>= 5.1)
haml_lint (0.62.0) haml_lint (0.59.0)
haml (>= 5.0) haml (>= 5.0)
parallel (~> 1.10) parallel (~> 1.10)
rainbow rainbow
@ -305,14 +298,13 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (2.3.0) http-form_data (2.3.0)
http_accept_language (2.1.1) http_accept_language (2.1.1)
httpclient (2.9.0) httpclient (2.8.3)
mutex_m
httplog (1.7.0) httplog (1.7.0)
rack (>= 2.0) rack (>= 2.0)
rainbow (>= 2.0.0) rainbow (>= 2.0.0)
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-tasks (1.0.15) i18n-tasks (1.0.14)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
ast (>= 2.1.0) ast (>= 2.1.0)
erubi erubi
@ -321,14 +313,13 @@ GEM
parser (>= 3.2.2.1) parser (>= 3.2.2.1)
rails-i18n rails-i18n
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.8, >= 1.8.1)
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.5) idn-ruby (0.1.5)
inline_svg (1.10.0) inline_svg (1.10.0)
activesupport (>= 3.0) activesupport (>= 3.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
io-console (0.8.0) io-console (0.8.0)
irb (1.15.2) irb (1.15.1)
pp (>= 0.6.0) pp (>= 0.6.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
@ -337,15 +328,13 @@ GEM
azure-blob (~> 0.5.2) azure-blob (~> 0.5.2)
hashie (~> 5.0) hashie (~> 5.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.10.2) json (2.9.1)
json-canonicalization (1.0.0) json-canonicalization (1.0.0)
json-jwt (1.16.7) json-jwt (1.15.3.1)
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
base64
bindata bindata
faraday (~> 2.0) httpclient
faraday-follow_redirects
json-ld (3.3.2) json-ld (3.3.2)
htmlentities (~> 4.3) htmlentities (~> 4.3)
json-canonicalization (~> 1.0) json-canonicalization (~> 1.0)
@ -361,7 +350,7 @@ GEM
addressable (~> 2.8) addressable (~> 2.8)
bigdecimal (~> 3.1) bigdecimal (~> 3.1)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jwt (2.10.1) jwt (2.9.3)
base64 base64
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
@ -382,10 +371,9 @@ GEM
mime-types mime-types
terrapin (>= 0.6.0, < 2.0) terrapin (>= 0.6.0, < 2.0)
language_server-protocol (3.17.0.4) language_server-protocol (3.17.0.4)
launchy (3.1.1) launchy (3.0.1)
addressable (~> 2.8) addressable (~> 2.8)
childprocess (~> 5.0) childprocess (~> 5.0)
logger (~> 1.6)
letter_opener (1.10.0) letter_opener (1.10.0)
launchy (>= 2.2, < 4) launchy (>= 2.2, < 4)
letter_opener_web (3.0.0) letter_opener_web (3.0.0)
@ -394,17 +382,10 @@ GEM
railties (>= 6.1) railties (>= 6.1)
rexml rexml
link_header (0.0.8) link_header (0.0.8)
lint_roller (1.1.0) llhttp-ffi (0.5.0)
linzer (0.6.5)
openssl (~> 3.0, >= 3.0.0)
rack (>= 2.2, < 4.0)
starry (~> 0.2)
stringio (~> 3.1, >= 3.1.2)
uri (~> 1.0, >= 1.0.2)
llhttp-ffi (0.5.1)
ffi-compiler (~> 1.0) ffi-compiler (~> 1.0)
rake (~> 13.0) rake (~> 13.0)
logger (1.7.0) logger (1.6.5)
lograge (0.14.0) lograge (0.14.0)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
@ -423,14 +404,14 @@ GEM
redis (>= 3.0.5) redis (>= 3.0.5)
matrix (0.4.2) matrix (0.4.2)
memory_profiler (1.1.0) memory_profiler (1.1.0)
mime-types (3.6.2) mime-types (3.6.0)
logger logger
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2025.0408) mime-types-data (3.2025.0107)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.8) mini_portile2 (2.8.8)
minitest (5.25.5) minitest (5.25.4)
msgpack (1.8.0) msgpack (1.7.5)
multi_json (1.15.0) multi_json (1.15.0)
mutex_m (0.3.0) mutex_m (0.3.0)
net-http (0.6.0) net-http (0.6.0)
@ -446,49 +427,47 @@ GEM
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.4)
nokogiri (1.18.7) nokogiri (1.18.3)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
oj (3.16.10) oj (3.16.9)
bigdecimal (>= 3.0) bigdecimal (>= 3.0)
ostruct (>= 0.2) ostruct (>= 0.2)
omniauth (2.1.3) omniauth (2.1.2)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 2.2.3) rack (>= 2.2.3)
rack-protection rack-protection
omniauth-cas (3.0.1) omniauth-cas (3.0.0)
addressable (~> 2.8) addressable (~> 2.8)
nokogiri (~> 1.12) nokogiri (~> 1.12)
omniauth (~> 2.1) omniauth (~> 2.1)
omniauth-rails_csrf_protection (1.0.2) omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2) actionpack (>= 4.2)
omniauth (~> 2.0) omniauth (~> 2.0)
omniauth-saml (2.2.3) omniauth-saml (2.2.1)
omniauth (~> 2.1) omniauth (~> 2.1)
ruby-saml (~> 1.18) ruby-saml (~> 1.17)
omniauth_openid_connect (0.8.0) omniauth_openid_connect (0.6.1)
omniauth (>= 1.9, < 3) omniauth (>= 1.9, < 3)
openid_connect (~> 2.2) openid_connect (~> 1.1)
openid_connect (2.3.1) openid_connect (1.4.2)
activemodel activemodel
attr_required (>= 1.0.0) attr_required (>= 1.0.0)
email_validator json-jwt (>= 1.15.0)
faraday (~> 2.0) net-smtp
faraday-follow_redirects rack-oauth2 (~> 1.21)
json-jwt (>= 1.16) swd (~> 1.3)
mail
rack-oauth2 (~> 2.2)
swd (~> 2.0)
tzinfo tzinfo
validate_email
validate_url validate_url
webfinger (~> 2.0) webfinger (~> 1.2)
openssl (3.3.0) openssl (3.2.1)
openssl-signature_algorithm (1.3.0) openssl-signature_algorithm (1.3.0)
openssl (> 2.0) openssl (> 2.0)
opentelemetry-api (1.5.0) opentelemetry-api (1.4.0)
opentelemetry-common (0.22.0) opentelemetry-common (0.21.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-exporter-otlp (0.30.0) opentelemetry-exporter-otlp (0.29.1)
google-protobuf (>= 3.18) google-protobuf (>= 3.18)
googleapis-common-protos-types (~> 1.3) googleapis-common-protos-types (~> 1.3)
opentelemetry-api (~> 1.1) opentelemetry-api (~> 1.1)
@ -501,7 +480,7 @@ GEM
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-active_support (~> 0.7) opentelemetry-instrumentation-active_support (~> 0.7)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-action_pack (0.12.0) opentelemetry-instrumentation-action_pack (0.11.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-rack (~> 0.21) opentelemetry-instrumentation-rack (~> 0.21)
@ -519,10 +498,6 @@ GEM
opentelemetry-instrumentation-active_record (0.9.0) opentelemetry-instrumentation-active_record (0.9.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-active_storage (0.1.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-active_support (~> 0.7)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-active_support (0.8.0) opentelemetry-instrumentation-active_support (0.8.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
@ -555,45 +530,44 @@ GEM
opentelemetry-instrumentation-rack (0.26.0) opentelemetry-instrumentation-rack (0.26.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-rails (0.36.0) opentelemetry-instrumentation-rails (0.35.1)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-action_mailer (~> 0.4.0) opentelemetry-instrumentation-action_mailer (~> 0.4.0)
opentelemetry-instrumentation-action_pack (~> 0.12.0) opentelemetry-instrumentation-action_pack (~> 0.11.0)
opentelemetry-instrumentation-action_view (~> 0.9.0) opentelemetry-instrumentation-action_view (~> 0.9.0)
opentelemetry-instrumentation-active_job (~> 0.8.0) opentelemetry-instrumentation-active_job (~> 0.8.0)
opentelemetry-instrumentation-active_record (~> 0.9.0) opentelemetry-instrumentation-active_record (~> 0.9.0)
opentelemetry-instrumentation-active_storage (~> 0.1.0)
opentelemetry-instrumentation-active_support (~> 0.8.0) opentelemetry-instrumentation-active_support (~> 0.8.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
opentelemetry-instrumentation-redis (0.26.1) opentelemetry-instrumentation-redis (0.26.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-sidekiq (0.26.1) opentelemetry-instrumentation-sidekiq (0.26.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-registry (0.4.0) opentelemetry-registry (0.3.1)
opentelemetry-api (~> 1.1) opentelemetry-api (~> 1.1)
opentelemetry-sdk (1.8.0) opentelemetry-sdk (1.6.0)
opentelemetry-api (~> 1.1) opentelemetry-api (~> 1.1)
opentelemetry-common (~> 0.20) opentelemetry-common (~> 0.20)
opentelemetry-registry (~> 0.2) opentelemetry-registry (~> 0.2)
opentelemetry-semantic_conventions opentelemetry-semantic_conventions
opentelemetry-semantic_conventions (1.11.0) opentelemetry-semantic_conventions (1.10.1)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostruct (0.6.1) ostruct (0.6.1)
ox (2.14.22) ox (2.14.21)
bigdecimal (>= 3.0) bigdecimal (>= 3.0)
parallel (1.27.0) parallel (1.26.3)
parser (3.3.8.0) parser (3.3.7.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
parslet (2.0.0) parslet (2.0.0)
pastel (0.8.0) pastel (0.8.0)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.5.9) pg (1.5.9)
pghero (3.6.2) pghero (3.6.1)
activerecord (>= 6.1) activerecord (>= 6.1)
pp (0.6.2) pp (0.6.2)
prettyprint prettyprint
@ -606,7 +580,6 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prettyprint (0.2.0) prettyprint (0.2.0)
prism (1.4.0)
prometheus_exporter (2.2.0) prometheus_exporter (2.2.0)
webrick webrick
propshaft (1.1.0) propshaft (1.1.0)
@ -618,22 +591,21 @@ GEM
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (6.6.0) puma (6.5.0)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.5.0) pundit (2.4.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (2.2.13) rack (2.2.11)
rack-attack (6.7.0) rack-attack (6.7.0)
rack (>= 1.0, < 4) rack (>= 1.0, < 4)
rack-cors (2.0.2) rack-cors (2.0.2)
rack (>= 2.0.0) rack (>= 2.0.0)
rack-oauth2 (2.2.1) rack-oauth2 (1.21.3)
activesupport activesupport
attr_required attr_required
faraday (~> 2.0) httpclient
faraday-follow_redirects
json-jwt (>= 1.11.0) json-jwt (>= 1.11.0)
rack (>= 2.1.0) rack (>= 2.1.0)
rack-protection (3.2.0) rack-protection (3.2.0)
@ -648,20 +620,24 @@ GEM
rackup (1.0.1) rackup (1.0.1)
rack (< 3) rack (< 3)
webrick webrick
rails (8.0.2) rails (8.0.1)
actioncable (= 8.0.2) actioncable (= 8.0.1)
actionmailbox (= 8.0.2) actionmailbox (= 8.0.1)
actionmailer (= 8.0.2) actionmailer (= 8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
actiontext (= 8.0.2) actiontext (= 8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activemodel (= 8.0.2) activemodel (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.2) railties (= 8.0.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@ -672,9 +648,9 @@ GEM
rails-i18n (8.0.1) rails-i18n (8.0.1)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9) railties (>= 8.0.0, < 9)
railties (8.0.2) railties (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -688,23 +664,23 @@ GEM
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.7.0) rdf-normalize (0.7.0)
rdf (~> 3.3) rdf (~> 3.3)
rdoc (6.13.1) rdoc (6.11.0)
psych (>= 4.0.0) psych (>= 4.0.0)
redcarpet (3.6.1) redcarpet (3.6.0)
redis (4.8.1) redis (4.8.1)
redis-namespace (1.11.0) redis-namespace (1.11.0)
redis (>= 4) redis (>= 4)
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.10.0) regexp_parser (2.10.0)
reline (0.6.1) reline (0.6.0)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.7.0) request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.4.1) rexml (3.4.0)
rotp (6.3.0) rotp (6.3.0)
rouge (4.5.1) rouge (4.5.1)
rpam2 (4.0.2) rpam2 (4.0.2)
@ -716,7 +692,7 @@ GEM
rspec-core (~> 3.13.0) rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0) rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0) rspec-mocks (~> 3.13.0)
rspec-core (3.13.3) rspec-core (3.13.2)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.3) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
@ -726,7 +702,7 @@ GEM
rspec-mocks (3.13.2) rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (7.1.1) rspec-rails (7.1.0)
actionpack (>= 7.0) actionpack (>= 7.0)
activesupport (>= 7.0) activesupport (>= 7.0)
railties (>= 7.0) railties (>= 7.0)
@ -734,52 +710,42 @@ GEM
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-sidekiq (5.1.0) rspec-sidekiq (5.0.0)
rspec-core (~> 3.0) rspec-core (~> 3.0)
rspec-expectations (~> 3.0) rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9) sidekiq (>= 5, < 8)
rspec-support (3.13.2) rspec-support (3.13.2)
rubocop (1.75.2) rubocop (1.71.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (>= 3.17.0)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0) rubocop-ast (>= 1.36.2, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1) rubocop-ast (1.38.0)
parser (>= 3.3.7.2) parser (>= 3.3.1.0)
prism (~> 1.4) rubocop-capybara (2.21.0)
rubocop-capybara (2.22.1) rubocop (~> 1.41)
lint_roller (~> 1.1) rubocop-performance (1.23.1)
rubocop (~> 1.72, >= 1.72.1) rubocop (>= 1.48.1, < 2.0)
rubocop-i18n (3.2.3) rubocop-ast (>= 1.31.1, < 2.0)
lint_roller (~> 1.1) rubocop-rails (2.29.1)
rubocop (>= 1.72.1)
rubocop-performance (1.25.0)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rails (2.31.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0) rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.5.0) rubocop-rspec (3.4.0)
lint_roller (~> 1.1) rubocop (~> 1.61)
rubocop (~> 1.72, >= 1.72.1) rubocop-rspec_rails (2.30.0)
rubocop-rspec_rails (2.31.0) rubocop (~> 1.61)
lint_roller (~> 1.1) rubocop-rspec (~> 3, >= 3.0.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-prof (1.7.1) ruby-prof (1.7.1)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-saml (1.18.0) ruby-saml (1.17.0)
nokogiri (>= 1.13.10) nokogiri (>= 1.13.10)
rexml rexml
ruby-vips (2.2.3) ruby-vips (2.2.3)
@ -797,7 +763,7 @@ GEM
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
securerandom (0.4.1) securerandom (0.4.1)
selenium-webdriver (4.31.0) selenium-webdriver (4.28.0)
base64 (~> 0.2) base64 (~> 0.2)
logger (~> 1.4) logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
@ -835,29 +801,26 @@ GEM
simplecov-lcov (0.8.0) simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
stackprof (0.2.27) stackprof (0.2.27)
starry (0.2.0) stoplight (4.1.0)
base64
stoplight (4.1.1)
redlock (~> 1.0) redlock (~> 1.0)
stringio (3.1.6) stringio (3.1.2)
strong_migrations (2.3.0) strong_migrations (2.1.0)
activerecord (>= 7) activerecord (>= 6.1)
swd (2.0.3) swd (1.3.0)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
faraday (~> 2.0) httpclient (>= 2.4)
faraday-follow_redirects
sysexits (1.2.0) sysexits (1.2.0)
temple (0.10.3) temple (0.10.3)
terminal-table (4.0.0) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 4) unicode-display_width (>= 1.1.1, < 3)
terrapin (1.1.0) terrapin (1.0.1)
climate_control climate_control
test-prof (1.4.4) test-prof (1.4.4)
thor (1.3.2) thor (1.3.2)
tilt (2.6.0) tilt (2.5.0)
timeout (0.4.3) timeout (0.4.3)
tpm-key_attestation (0.14.0) tpm-key_attestation (0.12.1)
bindata (~> 2.4) bindata (~> 2.4)
openssl (> 2.0) openssl (> 2.0)
openssl-signature_algorithm (~> 1.0) openssl-signature_algorithm (~> 1.0)
@ -876,34 +839,34 @@ GEM
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.2) tzinfo-data (1.2025.1)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.9.1) unf_ext (0.0.9.1)
unicode-display_width (3.1.4) unicode-display_width (2.6.0)
unicode-emoji (~> 4.0, >= 4.0.4) uri (1.0.2)
unicode-emoji (4.0.4)
uri (1.0.3)
useragent (0.16.11) useragent (0.16.11)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.15) validate_url (1.0.15)
activemodel (>= 3.0.0) activemodel (>= 3.0.0)
public_suffix public_suffix
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
webauthn (3.4.0) webauthn (3.2.2)
android_key_attestation (~> 0.3.0) android_key_attestation (~> 0.3.0)
bindata (~> 2.4) bindata (~> 2.4)
cbor (~> 0.5.9) cbor (~> 0.5.9)
cose (~> 1.1) cose (~> 1.1)
openssl (>= 2.2) openssl (>= 2.2)
safety_net_attestation (~> 0.4.0) safety_net_attestation (~> 0.4.0)
tpm-key_attestation (~> 0.14.0) tpm-key_attestation (~> 0.12.0)
webfinger (2.1.3) webfinger (1.2.0)
activesupport activesupport
faraday (~> 2.0) httpclient (>= 2.4)
faraday-follow_redirects webmock (3.24.0)
webmock (3.25.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@ -922,7 +885,7 @@ GEM
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.7.2) zeitwerk (2.7.1)
PLATFORMS PLATFORMS
ruby ruby
@ -931,7 +894,6 @@ DEPENDENCIES
active_model_serializers (~> 0.10) active_model_serializers (~> 0.10)
addressable (~> 2.8) addressable (~> 2.8)
annotaterb (~> 4.13) annotaterb (~> 4.13)
aws-sdk-core (< 3.216.0)
aws-sdk-s3 (~> 1.123) aws-sdk-s3 (~> 1.123)
better_errors (~> 2.9) better_errors (~> 2.9)
binding_of_caller (~> 1.0) binding_of_caller (~> 1.0)
@ -988,7 +950,6 @@ DEPENDENCIES
letter_opener (~> 1.8) letter_opener (~> 1.8)
letter_opener_web (~> 3.0) letter_opener_web (~> 3.0)
link_header (~> 0.0) link_header (~> 0.0)
linzer (~> 0.6.1)
lograge (~> 0.12) lograge (~> 0.12)
mail (~> 2.8) mail (~> 2.8)
mario-redis-lock (~> 1.2) mario-redis-lock (~> 1.2)
@ -1003,9 +964,9 @@ DEPENDENCIES
omniauth-cas (~> 3.0.0.beta.1) omniauth-cas (~> 3.0.0.beta.1)
omniauth-rails_csrf_protection (~> 1.0) omniauth-rails_csrf_protection (~> 1.0)
omniauth-saml (~> 2.0) omniauth-saml (~> 2.0)
omniauth_openid_connect (~> 0.8.0) omniauth_openid_connect (~> 0.6.1)
opentelemetry-api (~> 1.5.0) opentelemetry-api (~> 1.4.0)
opentelemetry-exporter-otlp (~> 0.30.0) opentelemetry-exporter-otlp (~> 0.29.0)
opentelemetry-instrumentation-active_job (~> 0.8.0) opentelemetry-instrumentation-active_job (~> 0.8.0)
opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) opentelemetry-instrumentation-active_model_serializers (~> 0.22.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
@ -1016,7 +977,7 @@ DEPENDENCIES
opentelemetry-instrumentation-net_http (~> 0.23.0) opentelemetry-instrumentation-net_http (~> 0.23.0)
opentelemetry-instrumentation-pg (~> 0.30.0) opentelemetry-instrumentation-pg (~> 0.30.0)
opentelemetry-instrumentation-rack (~> 0.26.0) opentelemetry-instrumentation-rack (~> 0.26.0)
opentelemetry-instrumentation-rails (~> 0.36.0) opentelemetry-instrumentation-rails (~> 0.35.0)
opentelemetry-instrumentation-redis (~> 0.26.0) opentelemetry-instrumentation-redis (~> 0.26.0)
opentelemetry-instrumentation-sidekiq (~> 0.26.0) opentelemetry-instrumentation-sidekiq (~> 0.26.0)
opentelemetry-sdk (~> 1.4) opentelemetry-sdk (~> 1.4)
@ -1035,6 +996,7 @@ DEPENDENCIES
rack-cors (~> 2.0) rack-cors (~> 2.0)
rack-test (~> 2.1) rack-test (~> 2.1)
rails (~> 8.0) rails (~> 8.0)
rails-controller-testing (~> 1.0)
rails-i18n (~> 8.0) rails-i18n (~> 8.0)
rdf-normalize (~> 0.5) rdf-normalize (~> 0.5)
redcarpet (~> 3.6) redcarpet (~> 3.6)
@ -1046,7 +1008,6 @@ DEPENDENCIES
rspec-sidekiq (~> 5.0) rspec-sidekiq (~> 5.0)
rubocop rubocop
rubocop-capybara rubocop-capybara
rubocop-i18n
rubocop-performance rubocop-performance
rubocop-rails rubocop-rails
rubocop-rspec rubocop-rspec
@ -1085,4 +1046,4 @@ RUBY VERSION
ruby 3.4.1p0 ruby 3.4.1p0
BUNDLED WITH BUNDLED WITH
2.6.8 2.6.3

124
README.md
View file

@ -1,27 +1,123 @@
NAS is an KMY & Mastodon Fork # ![kmyblue icon](https://raw.githubusercontent.com/kmycode/mastodon/kb_development/app/javascript/icons/favicon-32x32.png) kmyblue
The following are just a few of the most common features. There are many other minor changes to the specifications. [![Ruby Testing](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml)
Emoji reactions ! FOR ENGLISH USER ! We do not provide English documentation for kmyblue; we assume that you will use automatic translation software, such as Google, to translate the site.
Local Public (Does not appear on the federated timeline of remote servers, but does appear on followers' home timelines. This is different from local only) kmyblueは、ActivityPubに接続するSNSの1つである[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のためのMastodonを目指して開発しました。
Bookmark classification kmyblueはフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしてのkmyblueをさします。
Set who can search your posts for each post (Searchability) kmyblueは AGPL ライセンスで公開されているため、どなたでも自由にフォークし、このソースコードを元に自分でサーバーを立てて公開することができます。確かにサーバーkmyblueは創作作家向けの利用規約が設定されていますが、フォークとしてのkmyblueのルールは全くの別物です。いかなるコミュニティにも平等にお使いいただけます。
kmyblueは、閉鎖的なコミュニティ、あまり目立ちたくないコミュニティには特に強力な機能を提供します。kmyblueはプライバシーを考慮したうえで強力な独自機能を提供するため、汎用サーバーとして利用するにもある程度十分な機能が揃っています。
Quote posts, modest quotes (references) テストコード、Lint どちらも動いています。
Record posts that meet certain conditions such as domains, accounts, and keywords (Subscriptions/Antennas) ### アジェンダ
Send posts to a designated set of followers (Circles) (different from direct messages) - 利用方法
- kmyblueの開発方針
- kmyblueは何でないか
- kmyblueの独自機能
- 英語のサポートについて
Notification of new posts on lists ## 利用方法
Exclude posts from people you follow when filtering posts ### インストール方法
Hide number of followers and followings [Wiki](https://github.com/kmycode/mastodon/wiki/Installation)を参照してください。
Automatically delete posts after a specified time has passed ### 開発への参加方法
Expanding moderation functions CONTRIBUTING.mdを参照してください。
### テスト
```
# デバッグ実行(以下のいずれか)
foreman start
DB_USER=postgres DB_PASS=password foreman start
# 一部を除く全てのテストを行う
RAILS_ENV=test bundle exec rspec spec
# ElasticSearch連携テストを行う
RAILS_ENV=test ES_ENABLED=true bundle exec rspec --tag search
RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search
```
## kmyblueの開発方針
### 本家Mastodonへの積極的追従
kmyblueは、追加機能を控えめにする代わりに本家Mastodonに積極的に追従を行います。kmyblueの追加機能そのままに、Mastodonの新機能も利用できるよう調整を行います。
### ゆるやかな内輪での運用
kmyblueは同人向けサーバーとして出発したため、同人作家に需要のある「内輪リを外部にできるだけもらさない」という部分に特化しています。
「ローカル公開」は、投稿を見せたくない人に見つかりにくくする効果があります。「サークル」は、フォロワーの中でも特に見せたい人だけに見せる効果があります。
「検索許可」という独自の検索オプションを利用することで、公開投稿の一部だけを検索されにくくするだけでなく、非収載投稿が誰でも自由に検索できるようになります。
内輪とは自分のサーバーに限ったものではありません。内輪同士で複数のサーバーを運営するとき、お互いが深く繋がれる「フレンドサーバー」というシステムも用意しています。
### 少人数サーバーでの運用
kmyblueは、人の少ないサーバーでの運用を考慮して設計しています。そのため、Fedibirdにあるような、人の多いサーバー向けの機能はあまり作っていません。
サーバーの負荷については一部度外視している部分があります。たとえば絵文字リアクション機能はサーバーへ著しい負荷をかける場合があります。ただしkmyblueでは、絵文字リアクション機能そのものを無効にしたり、負荷の高いストリーミング処理を無効にする管理者オプションも存在します。
もちろん人の多いサーバーでの運用が不便になるような修正は行っていません。人数にかかわらず、そのままお使いいただけます。
### 比較的高い防御力
kmyblueでは、「Fediverseは将来的に荒むのではないか」「Fediverseは将来的にスパムに溢れるのではないか」を念頭に設計している部分があります。投稿だけでなく絵文字リアクションも対象にした防衛策があります。
管理者は「NGワード」「NGルール」機能の利用が可能です。設定を変更することで、一部のモデレーターもこの機能を利用できます。
利用者は、独自拡張されたフィルター機能、絵文字リアクションのブロックなどを利用できます。
ただし防御力の高さは自由を犠牲にします。例えばNGワードが多すぎると、他のサーバーからの投稿が制限され、かつそれに気づきにくくなります。
## kmyblueは何でないか
kmyblueは、企業・政府機関向けに開発されたものではありません。開発者はセキュリティに関する専門知識を有しておらず、高度なセキュリティを求められる機関向けのソフトウェアを制作する能力はありません。また、kmyblueのメンテナは現在1人のみであり、そのメンテナが飽きたら開発がストップするリスクも高いです。Mastodonのような高い信頼性・安全性を保証することはできないので、導入の際はご自身で安全を十分に確認してからお使いになることを強くおすすめします。
個人サーバーであっても、安定性を強く求める方にはおすすめできません。glitch-socがよりよい選択肢になるでしょう。
kmyblueは、Misskeyではありません。Misskeyは「楽しむ」をコンセプトにしていますが、kmyblueはMastodonの思想を受け継ぎ、炎上や喧騒を避けることのできる落ち着いた場所を目指しています。そのため、思想に合わない機能は実装しないか、大幅に弱体化しています。
kmyblueは、Fedibirdではありません。Fedibirdは大規模サーバー向けに設定していると思われる機能があり、例えば購読機能がその代表例です。Fedibirdの購読は擬似的なフォロー体験を与えるものですが、本物のフォローではないため、購読対象の投稿が配送されることを確約したものではありません。小規模サーバーだとかえって不便になる機能を、kmyblueは避けています。
## kmyblueの独自機能
以下に列挙したものはあくまで代表的なものです。これ以外にも、細かい仕様変更などが多数含まれます。
- 絵文字リアクション
- ローカル公開Local Publicリモートサーバーの連合タイムラインには流れませんが、フォロワーのホームタイムラインには流れます。**ローカル限定とは異なります**
- ブックマークの分類
- 自分の投稿を検索できる人を投稿ごとに設定検索許可・Searchability
- 投稿の引用、ひかえめな引用(参照)
- ドメイン・アカウント・キーワードなど特定条件を満たした投稿を記録する機能(購読・アンテナ)
- フォロワーの一部を指名して投稿を送る機能(サークル)(ダイレクトメッセージとは異なります)
- リスト新着投稿の通知
- 投稿のフィルタリングにおいて、自分がフォローしている相手の投稿を除外
- フォロー・フォロワー数を隠す機能
- 指定した時間が経過したあとに投稿を自動削除する機能
- モデレーション機能の拡張
## 英語のサポートについて
kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、英語の読み書きがほとんどできません。そのため、ドキュメントの英語化、海外向け公式アカウントの新設などを行う予定はありません。
要望やバグ報告はIssueに書いて構いませんが、Issue画面内の説明やテンプレートはすべて日本語になっています。投稿が難しければ、Discussionに投稿してください。こちらで必要と判断したものは、改めてIssueとして起票します。
そのほか開発者へ質問があれば、[@askyq@kmy.blue](https://kmy.blue/@askyq)へ英語のまま送ってください。
ただしkmyblueのドキュメント、[@askyq@kmy.blue](https://kmy.blue/@askyq)内のkmyblueフォークに関係する投稿を、許可なく翻訳して公開することは問題ありません。
## 開発者のアカウントについて
kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、用途別にアカウントを分けるようなことはせず、すべての発言をつのアカウントで行っています。そのため、kmyblueの開発だけでなく、成人向け同人作品の話も混ざっています。
このうち、公開範囲「公開」「ローカル公開」「非収載」であるkmyblueフォークの開発に関係する投稿に限り抽出し、翻訳の有無に関係なく公開することを許可します。これはkmyblueフォークの利用者にとって公共性の高いコンテンツであると思われます。これは、日本と欧米では一般的に考えられている児童ポルの基準が異なり、欧米のサーバーの中にはこのアカウントをフォローしづらいものもあるという懸念を考慮したものです。

View file

@ -1,18 +0,0 @@
# frozen_string_literal: true
class Admin::Announcements::DistributionsController < Admin::BaseController
before_action :set_announcement
def create
authorize @announcement, :distribute?
@announcement.touch(:notification_sent_at)
Admin::DistributeAnnouncementNotificationWorker.perform_async(@announcement.id)
redirect_to admin_announcements_path
end
private
def set_announcement
@announcement = Announcement.find(params[:announcement_id])
end
end

View file

@ -1,16 +0,0 @@
# frozen_string_literal: true
class Admin::Announcements::PreviewsController < Admin::BaseController
before_action :set_announcement
def show
authorize @announcement, :distribute?
@user_count = @announcement.scope_for_notification.count
end
private
def set_announcement
@announcement = Announcement.find(params[:announcement_id])
end
end

View file

@ -1,17 +0,0 @@
# frozen_string_literal: true
class Admin::Announcements::TestsController < Admin::BaseController
before_action :set_announcement
def create
authorize @announcement, :distribute?
UserMailer.announcement_published(current_user, @announcement).deliver_later!
redirect_to admin_announcements_path
end
private
def set_announcement
@announcement = Announcement.find(params[:announcement_id])
end
end

View file

@ -7,12 +7,17 @@ module Admin
layout 'admin' layout 'admin'
before_action :set_cache_headers
before_action :set_referrer_policy_header before_action :set_referrer_policy_header
after_action :verify_authorized after_action :verify_authorized
private private
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def set_referrer_policy_header def set_referrer_policy_header
response.headers['Referrer-Policy'] = 'same-origin' response.headers['Referrer-Policy'] = 'same-origin'
end end

View file

@ -1,20 +0,0 @@
# frozen_string_literal: true
class Admin::Fasp::Debug::CallbacksController < Admin::BaseController
def index
authorize [:admin, :fasp, :provider], :update?
@callbacks = Fasp::DebugCallback
.includes(:fasp_provider)
.order(created_at: :desc)
end
def destroy
authorize [:admin, :fasp, :provider], :update?
callback = Fasp::DebugCallback.find(params[:id])
callback.destroy
redirect_to admin_fasp_debug_callbacks_path
end
end

View file

@ -1,19 +0,0 @@
# frozen_string_literal: true
class Admin::Fasp::DebugCallsController < Admin::BaseController
before_action :set_provider
def create
authorize [:admin, @provider], :update?
@provider.perform_debug_call
redirect_to admin_fasp_providers_path
end
private
def set_provider
@provider = Fasp::Provider.find(params[:provider_id])
end
end

View file

@ -1,47 +0,0 @@
# frozen_string_literal: true
class Admin::Fasp::ProvidersController < Admin::BaseController
before_action :set_provider, only: [:show, :edit, :update, :destroy]
def index
authorize [:admin, :fasp, :provider], :index?
@providers = Fasp::Provider.order(confirmed: :asc, created_at: :desc)
end
def show
authorize [:admin, @provider], :show?
end
def edit
authorize [:admin, @provider], :update?
end
def update
authorize [:admin, @provider], :update?
if @provider.update(provider_params)
redirect_to admin_fasp_providers_path
else
render :edit
end
end
def destroy
authorize [:admin, @provider], :destroy?
@provider.destroy
redirect_to admin_fasp_providers_path
end
private
def provider_params
params.expect(fasp_provider: [capabilities_attributes: {}])
end
def set_provider
@provider = Fasp::Provider.find(params[:id])
end
end

View file

@ -1,23 +0,0 @@
# frozen_string_literal: true
class Admin::Fasp::RegistrationsController < Admin::BaseController
before_action :set_provider
def new
authorize [:admin, @provider], :create?
end
def create
authorize [:admin, @provider], :create?
@provider.update_info!(confirm: true)
redirect_to edit_admin_fasp_provider_path(@provider)
end
private
def set_provider
@provider = Fasp::Provider.find(params[:provider_id])
end
end

View file

@ -6,7 +6,7 @@ module Admin
def index def index
authorize :software_update, :index? authorize :software_update, :index?
@software_updates = SoftwareUpdate.by_version.filter(&:pending?) @software_updates = SoftwareUpdate.by_version
end end
private private

View file

@ -23,7 +23,7 @@ class Admin::TermsOfService::DraftsController < Admin::BaseController
private private
def set_terms_of_service def set_terms_of_service
@terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text, effective_date: 10.days.from_now) @terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text)
end end
def current_terms_of_service def current_terms_of_service
@ -32,6 +32,6 @@ class Admin::TermsOfService::DraftsController < Admin::BaseController
def resource_params def resource_params
params params
.expect(terms_of_service: [:text, :changelog, :effective_date]) .expect(terms_of_service: [:text, :changelog])
end end
end end

View file

@ -3,6 +3,6 @@
class Admin::TermsOfServiceController < Admin::BaseController class Admin::TermsOfServiceController < Admin::BaseController
def index def index
authorize :terms_of_service, :index? authorize :terms_of_service, :index?
@terms_of_service = TermsOfService.published.first @terms_of_service = TermsOfService.live.first
end end
end end

View file

@ -1,81 +0,0 @@
# frozen_string_literal: true
class Api::Fasp::BaseController < ApplicationController
class Error < ::StandardError; end
DIGEST_PATTERN = /sha-256=:(.*?):/
KEYID_PATTERN = /keyid="(.*?)"/
attr_reader :current_provider
skip_forgery_protection
before_action :check_fasp_enabled
before_action :require_authentication
after_action :sign_response
private
def require_authentication
validate_content_digest!
validate_signature!
rescue Error, Linzer::Error, ActiveRecord::RecordNotFound => e
logger.debug("FASP Authentication error: #{e}")
authentication_error
end
def authentication_error
respond_to do |format|
format.json { head 401 }
end
end
def validate_content_digest!
content_digest_header = request.headers['content-digest']
raise Error, 'content-digest missing' if content_digest_header.blank?
digest_received = content_digest_header.match(DIGEST_PATTERN)[1]
digest_computed = OpenSSL::Digest.base64digest('sha256', request.body&.string || '')
raise Error, 'content-digest does not match' if digest_received != digest_computed
end
def validate_signature!
signature_input = request.headers['signature-input']&.encode('UTF-8')
raise Error, 'signature-input is missing' if signature_input.blank?
keyid = signature_input.match(KEYID_PATTERN)[1]
provider = Fasp::Provider.find(keyid)
linzer_request = Linzer.new_request(
request.method,
request.original_url,
{},
{
'content-digest' => request.headers['content-digest'],
'signature-input' => signature_input,
'signature' => request.headers['signature'],
}
)
message = Linzer::Message.new(linzer_request)
key = Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
signature = Linzer::Signature.build(message.headers)
Linzer.verify(key, message, signature)
@current_provider = provider
end
def sign_response
response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
linzer_response = Linzer.new_response(response.body, response.status, { 'content-digest' => response.headers['content-digest'] })
message = Linzer::Message.new(linzer_response)
key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
signature = Linzer.sign(key, message, %w(@status content-digest))
response.headers.merge!(signature.to_h)
end
def check_fasp_enabled
raise ActionController::RoutingError unless Mastodon::Feature.fasp_enabled?
end
end

View file

@ -1,15 +0,0 @@
# frozen_string_literal: true
class Api::Fasp::Debug::V0::Callback::ResponsesController < Api::Fasp::BaseController
def create
Fasp::DebugCallback.create(
fasp_provider: current_provider,
ip: request.remote_ip,
request_body: request.raw_post
)
respond_to do |format|
format.json { head 201 }
end
end
end

View file

@ -1,26 +0,0 @@
# frozen_string_literal: true
class Api::Fasp::RegistrationsController < Api::Fasp::BaseController
skip_before_action :require_authentication
def create
@current_provider = Fasp::Provider.create!(
name: params[:name],
base_url: params[:baseUrl],
remote_identifier: params[:serverId],
provider_public_key_base64: params[:publicKey]
)
render json: registration_confirmation
end
private
def registration_confirmation
{
faspId: current_provider.id.to_s,
publicKey: current_provider.server_public_key_base64,
registrationCompletionUri: new_admin_fasp_provider_registration_url(current_provider),
}
end
end

View file

@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
@account = current_account @account = current_account
UpdateAccountService.new.call(@account, account_params, raise_error: true) UpdateAccountService.new.call(@account, account_params, raise_error: true)
current_user.update(user_params) if user_params current_user.update(user_params) if user_params
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
render json: @account, serializer: REST::CredentialAccountSerializer render json: @account, serializer: REST::CredentialAccountSerializer
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e).as_json, status: 422 render json: ValidationErrorFormatter.new(e).as_json, status: 422

View file

@ -1,66 +0,0 @@
# frozen_string_literal: true
class Api::V1::Accounts::EndorsementsController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :index
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
before_action :require_user!, except: :index
before_action :set_account
before_action :set_endorsed_accounts, only: :index
after_action :insert_pagination_headers, only: :index
def index
cache_if_unauthenticated!
render json: @endorsed_accounts, each_serializer: REST::AccountSerializer
end
def create
AccountPin.find_or_create_by!(account: current_account, target_account: @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
end
def destroy
pin = AccountPin.find_by(account: current_account, target_account: @account)
pin&.destroy!
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
end
private
def set_account
@account = Account.find(params[:account_id])
end
def set_endorsed_accounts
@endorsed_accounts = @account.unavailable? ? [] : paginated_endorsed_accounts
end
def paginated_endorsed_accounts
@account.endorsed_accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
def relationships_presenter
AccountRelationshipsPresenter.new([@account], current_user.account_id)
end
def next_path
api_v1_account_endorsements_url pagination_params(max_id: pagination_max_id) if records_continue?
end
def prev_path
api_v1_account_endorsements_url pagination_params(since_id: pagination_since_id) unless @endorsed_accounts.empty?
end
def pagination_collection
@endorsed_accounts
end
def records_continue?
@endorsed_accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
end

View file

@ -17,6 +17,6 @@ class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
end end
def set_featured_tags def set_featured_tags
@featured_tags = @account.unavailable? ? [] : @account.featured_tags @featured_tags = @account.suspended? ? [] : @account.featured_tags
end end
end end

View file

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Accounts::IdentityProofsController < Api::BaseController class Api::V1::Accounts::IdentityProofsController < Api::BaseController
include DeprecationConcern
deprecate_api '2022-03-30'
before_action :require_user! before_action :require_user!
before_action :set_account before_action :set_account

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
class Api::V1::Accounts::PinsController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
before_action :require_user!
before_action :set_account
def create
AccountPin.find_or_create_by!(account: current_account, target_account: @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
end
def destroy
pin = AccountPin.find_by(account: current_account, target_account: @account)
pin&.destroy!
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
end
private
def set_account
@account = Account.find(params[:account_id])
end
def relationships_presenter
AccountRelationshipsPresenter.new([@account], current_user.account_id)
end
end

View file

@ -124,7 +124,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code, :date_of_birth) params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
end end
def invite def invite

View file

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::FiltersController < Api::BaseController class Api::V1::FiltersController < Api::BaseController
include DeprecationConcern
deprecate_api '2022-11-14'
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
before_action :require_user! before_action :require_user!

View file

@ -5,18 +5,12 @@ class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseCo
def show def show
cache_even_if_authenticated! cache_even_if_authenticated!
render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer render json: @terms_of_service, serializer: REST::PrivacyPolicySerializer
end end
private private
def set_terms_of_service def set_terms_of_service
@terms_of_service = begin @terms_of_service = TermsOfService.live.first!
if params[:date].present?
TermsOfService.published.find_by!(effective_date: params[:date])
else
TermsOfService.live.first || TermsOfService.published.first! # For the case when none of the published terms have become effective yet
end
end
end end
end end

View file

@ -1,9 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::InstancesController < Api::V2::InstancesController class Api::V1::InstancesController < Api::BaseController
include DeprecationConcern skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
deprecate_api '2022-11-14' vary_by ''
# Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user
super if limited_federation_mode?
end
def show def show
cache_even_if_authenticated! cache_even_if_authenticated!

View file

@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_list, except: [:index, :create] before_action :set_list, except: [:index, :create]
rescue_from ArgumentError do |e|
render json: { error: e.to_s }, status: 422
end
def index def index
@lists = List.where(account: current_account).all @lists = List.where(account: current_account).all
render json: @lists, each_serializer: REST::ListSerializer render json: @lists, each_serializer: REST::ListSerializer

View file

@ -3,8 +3,8 @@
class Api::V1::MediaController < Api::BaseController class Api::V1::MediaController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action -> { doorkeeper_authorize! :write, :'write:media' }
before_action :require_user! before_action :require_user!
before_action :set_media_attachment, except: [:create, :destroy] before_action :set_media_attachment, except: [:create]
before_action :check_processing, except: [:create, :destroy] before_action :check_processing, except: [:create]
def show def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
@ -25,15 +25,6 @@ class Api::V1::MediaController < Api::BaseController
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end end
def destroy
@media_attachment = current_account.media_attachments.find(params[:id])
return render json: in_usage_error, status: 422 unless @media_attachment.status_id.nil?
@media_attachment.destroy
render_empty
end
private private
def status_code_for_media_attachment def status_code_for_media_attachment
@ -63,8 +54,4 @@ class Api::V1::MediaController < Api::BaseController
def processing_error def processing_error
{ error: 'Error processing thumbnail for uploaded media' } { error: 'Error processing thumbnail for uploaded media' }
end end
def in_usage_error
{ error: 'Media attachment is currently used by a status' }
end
end end

View file

@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
def destroy def destroy
@account = current_account @account = current_account
UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true) UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
render json: @account, serializer: REST::CredentialAccountSerializer render json: @account, serializer: REST::CredentialAccountSerializer
end end
end end

View file

@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
def destroy def destroy
@account = current_account @account = current_account
UpdateAccountService.new.call(@account, { header: nil }, raise_error: true) UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
render json: @account, serializer: REST::CredentialAccountSerializer render json: @account, serializer: REST::CredentialAccountSerializer
end end
end end

View file

@ -67,8 +67,6 @@ class Api::V1::StatusesController < Api::BaseController
statuses = [@status] + @context.ancestors + @context.descendants + @context.references statuses = [@status] + @context.ancestors + @context.descendants + @context.references
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
end end
def create def create
@ -127,7 +125,7 @@ class Api::V1::StatusesController < Api::BaseController
@status.account.statuses_count = @status.account.statuses_count - 1 @status.account.statuses_count = @status.account.statuses_count - 1
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) }) RemovalWorker.perform_async(@status.id, { 'redraft' => true })
render json: json render json: json
end end

View file

@ -2,9 +2,6 @@
class Api::V1::SuggestionsController < Api::BaseController class Api::V1::SuggestionsController < Api::BaseController
include Authorization include Authorization
include DeprecationConcern
deprecate_api '2021-05-16', only: [:index]
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index

View file

@ -1,15 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Trends::TagsController < Api::BaseController class Api::V1::Trends::TagsController < Api::BaseController
include DeprecationConcern
before_action :set_tags before_action :set_tags
after_action :insert_pagination_headers after_action :insert_pagination_headers
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i DEFAULT_TAGS_LIMIT = 10
deprecate_api '2022-03-30', only: :index, if: -> { request.path == '/api/v1/trends' }
def index def index
cache_if_unauthenticated! cache_if_unauthenticated!

View file

@ -1,16 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V2::InstancesController < Api::BaseController class Api::V2::InstancesController < Api::V1::InstancesController
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 limited federation mode
def current_user
super if limited_federation_mode?
end
def show def show
cache_even_if_authenticated! cache_even_if_authenticated!
render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance' render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance'

View file

@ -12,6 +12,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_sessions, only: [:edit, :update] before_action :set_sessions, only: [:edit, :update]
before_action :set_strikes, only: [:edit, :update] before_action :set_strikes, only: [:edit, :update]
before_action :require_not_suspended!, only: [:update] before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :update]
before_action :set_rules, only: :new before_action :set_rules, only: :new
before_action :require_rules_acceptance!, only: :new before_action :require_rules_acceptance!, only: :new
before_action :set_registration_form_time, only: :new before_action :set_registration_form_time, only: :new
@ -62,7 +63,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def configure_sign_up_params def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |user_params| devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password, :date_of_birth) user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
end end
end end
@ -138,6 +139,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
set_locale { render :rules } set_locale { render :rules }
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def is_flashing_format? # rubocop:disable Naming/PredicateName def is_flashing_format? # rubocop:disable Naming/PredicateName
if params[:action] == 'create' if params[:action] == 'create'
false # Disable flash messages for sign-up false # Disable flash messages for sign-up

View file

@ -174,7 +174,7 @@ class Auth::SessionsController < Devise::SessionsController
end end
def disable_custom_css? def disable_custom_css?
user_params[:disable_css].present? && user_params[:disable_css] == '1' user_params[:disable_css].present? && user_params[:disable_css] != '0'
end end
def disable_custom_css!(user) def disable_custom_css!(user)

View file

@ -9,15 +9,13 @@ class BackupsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_backup before_action :set_backup
BACKUP_LINK_TIMEOUT = 1.hour.freeze
def download def download
case Paperclip::Attachment.default_options[:storage] case Paperclip::Attachment.default_options[:storage]
when :s3, :azure when :s3, :azure
redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.to_i), allow_other_host: true redirect_to @backup.dump.expiring_url(10), allow_other_host: true
when :fog when :fog
if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present? if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.from_now), allow_other_host: true redirect_to @backup.dump.expiring_url(Time.now.utc + 10), allow_other_host: true
else else
redirect_to full_asset_url(@backup.dump.url), allow_other_host: true redirect_to full_asset_url(@backup.dump.url), allow_other_host: true
end end

View file

@ -1,17 +0,0 @@
# frozen_string_literal: true
module DeprecationConcern
extend ActiveSupport::Concern
class_methods do
def deprecate_api(date, sunset: nil, **kwargs)
deprecation_timestamp = "@#{date.to_datetime.to_i}"
sunset = sunset&.to_date&.httpdate
before_action(**kwargs) do
response.headers['Deprecation'] = deprecation_timestamp
response.headers['Sunset'] = sunset if sunset
end
end
end
end

View file

@ -10,6 +10,8 @@ module SignatureVerification
EXPIRATION_WINDOW_LIMIT = 12.hours EXPIRATION_WINDOW_LIMIT = 12.hours
CLOCK_SKEW_MARGIN = 1.hour CLOCK_SKEW_MARGIN = 1.hour
class SignatureVerificationError < StandardError; end
def require_account_signature! def require_account_signature!
render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
end end
@ -32,7 +34,7 @@ module SignatureVerification
def signature_key_id def signature_key_id
signature_params['keyId'] signature_params['keyId']
rescue Mastodon::SignatureVerificationError rescue SignatureVerificationError
nil nil
end end
@ -43,17 +45,17 @@ module SignatureVerification
def signed_request_actor def signed_request_actor
return @signed_request_actor if defined?(@signed_request_actor) return @signed_request_actor if defined?(@signed_request_actor)
raise Mastodon::SignatureVerificationError, 'Request not signed' unless signed_request? raise SignatureVerificationError, 'Request not signed' unless signed_request?
raise Mastodon::SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters? raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
raise Mastodon::SignatureVerificationError, 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)' unless %w(rsa-sha256 hs2019).include?(signature_algorithm) raise SignatureVerificationError, 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)' unless %w(rsa-sha256 hs2019).include?(signature_algorithm)
raise Mastodon::SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window? raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
verify_signature_strength! verify_signature_strength!
verify_body_digest! verify_body_digest!
actor = actor_from_key_id(signature_params['keyId']) actor = actor_from_key_id(signature_params['keyId'])
raise Mastodon::SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
signature = Base64.decode64(signature_params['signature']) signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string(include_query_string: true) compare_signed_string = build_signed_string(include_query_string: true)
@ -66,7 +68,7 @@ module SignatureVerification
actor = stoplight_wrapper.run { actor_refresh_key!(actor) } actor = stoplight_wrapper.run { actor_refresh_key!(actor) }
raise Mastodon::SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
compare_signed_string = build_signed_string(include_query_string: true) compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
@ -76,7 +78,7 @@ module SignatureVerification
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature'] fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
rescue Mastodon::SignatureVerificationError => e rescue SignatureVerificationError => e
fail_with! e.message fail_with! e.message
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
fail_with! "Failed to fetch remote data: #{e.message}" fail_with! "Failed to fetch remote data: #{e.message}"
@ -102,7 +104,7 @@ module SignatureVerification
def signature_params def signature_params
@signature_params ||= SignatureParser.parse(request.headers['Signature']) @signature_params ||= SignatureParser.parse(request.headers['Signature'])
rescue SignatureParser::ParsingError rescue SignatureParser::ParsingError
raise Mastodon::SignatureVerificationError, 'Error parsing signature parameters' raise SignatureVerificationError, 'Error parsing signature parameters'
end end
def signature_algorithm def signature_algorithm
@ -114,31 +116,31 @@ module SignatureVerification
end end
def verify_signature_strength! def verify_signature_strength!
raise Mastodon::SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)') raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
raise Mastodon::SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest') raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
raise Mastodon::SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host') raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
raise Mastodon::SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest') raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end end
def verify_body_digest! def verify_body_digest!
return unless signed_headers.include?('digest') return unless signed_headers.include?('digest')
raise Mastodon::SignatureVerificationError, 'Digest header missing' unless request.headers.key?('Digest') raise SignatureVerificationError, 'Digest header missing' unless request.headers.key?('Digest')
digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] } digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
sha256 = digests.assoc('sha-256') sha256 = digests.assoc('sha-256')
raise Mastodon::SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil? raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
return if body_digest == sha256[1] return if body_digest == sha256[1]
digest_size = begin digest_size = begin
Base64.strict_decode64(sha256[1].strip).length Base64.strict_decode64(sha256[1].strip).length
rescue ArgumentError rescue ArgumentError
raise Mastodon::SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}" raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}"
end end
raise Mastodon::SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32 raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32
raise Mastodon::SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
end end
def verify_signature(actor, signature, compare_signed_string) def verify_signature(actor, signature, compare_signed_string)
@ -163,13 +165,13 @@ module SignatureVerification
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" "#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end end
when '(created)' when '(created)'
raise Mastodon::SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise Mastodon::SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank? raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
"(created): #{signature_params['created']}" "(created): #{signature_params['created']}"
when '(expires)' when '(expires)'
raise Mastodon::SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise Mastodon::SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank? raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
"(expires): #{signature_params['expires']}" "(expires): #{signature_params['expires']}"
else else
@ -191,7 +193,7 @@ module SignatureVerification
expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present? expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present?
rescue ArgumentError => e rescue ArgumentError => e
raise Mastodon::SignatureVerificationError, "Invalid Date header: #{e.message}" raise SignatureVerificationError, "Invalid Date header: #{e.message}"
end end
expires_time ||= created_time + 5.minutes unless created_time.nil? expires_time ||= created_time + 5.minutes unless created_time.nil?
@ -231,9 +233,9 @@ module SignatureVerification
account account
end end
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e
raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e
raise Mastodon::SignatureVerificationError, e.message raise SignatureVerificationError, e.message
end end
def stoplight_wrapper def stoplight_wrapper
@ -249,8 +251,8 @@ module SignatureVerification
ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false) ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false)
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e
raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
raise Mastodon::SignatureVerificationError, e.message raise SignatureVerificationError, e.message
end end
end end

View file

@ -46,6 +46,6 @@ module WebAppControllerConcern
protected protected
def set_referer_header def set_referer_header
response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'strict-origin-when-cross-origin' : 'same-origin') response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'origin' : 'same-origin')
end end
end end

View file

@ -8,4 +8,11 @@ class Disputes::BaseController < ApplicationController
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_cache_headers
private
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -6,6 +6,7 @@ class Filters::StatusesController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_filter before_action :set_filter
before_action :set_status_filters before_action :set_status_filters
before_action :set_cache_headers
PER_PAGE = 20 PER_PAGE = 20
@ -39,4 +40,8 @@ class Filters::StatusesController < ApplicationController
def action_from_button def action_from_button
'remove' if params[:remove] 'remove' if params[:remove]
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -5,6 +5,7 @@ class FiltersController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_filter, only: [:edit, :update, :destroy] before_action :set_filter, only: [:edit, :update, :destroy]
before_action :set_cache_headers
def index def index
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase) @filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
@ -49,4 +50,8 @@ class FiltersController < ApplicationController
def resource_params def resource_params
params.expect(custom_filter: [:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [[:id, :keyword, :whole_word, :_destroy]]]) params.expect(custom_filter: [:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [[:id, :keyword, :whole_word, :_destroy]]])
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -6,6 +6,7 @@ class InvitesController < ApplicationController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_cache_headers
def index def index
authorize :invite, :create? authorize :invite, :create?
@ -44,4 +45,8 @@ class InvitesController < ApplicationController
def resource_params def resource_params
params.expect(invite: [:max_uses, :expires_in, :autofollow, :comment]) params.expect(invite: [:max_uses, :expires_in, :autofollow, :comment])
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -5,6 +5,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :store_current_location before_action :store_current_location
before_action :authenticate_resource_owner! before_action :authenticate_resource_owner!
before_action :set_cache_headers
content_security_policy do |p| content_security_policy do |p|
p.form_action(false) p.form_action(false)
@ -31,4 +32,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
def truthy_param?(key) def truthy_param?(key)
ActiveModel::Type::Boolean.new.cast(params[key]) ActiveModel::Type::Boolean.new.cast(params[key])
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -6,6 +6,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location before_action :store_current_location
before_action :authenticate_resource_owner! before_action :authenticate_resource_owner!
before_action :require_not_suspended!, only: :destroy before_action :require_not_suspended!, only: :destroy
before_action :set_cache_headers
before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json } before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json }
@ -29,6 +30,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
forbidden if current_account.unavailable? forbidden if current_account.unavailable?
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def set_last_used_at_by_app def set_last_used_at_by_app
@last_used_at_by_app = current_resource_owner.applications_last_used @last_used_at_by_app = current_resource_owner.applications_last_used
end end

View file

@ -6,6 +6,7 @@ class RelationshipsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_accounts, only: :show before_action :set_accounts, only: :show
before_action :set_relationships, only: :show before_action :set_relationships, only: :show
before_action :set_cache_headers
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship? helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@ -65,4 +66,8 @@ class RelationshipsController < ApplicationController
'remove_domains_from_followers' 'remove_domains_from_followers'
end end
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -2,6 +2,7 @@
class Settings::ApplicationsController < Settings::BaseController class Settings::ApplicationsController < Settings::BaseController
before_action :set_application, only: [:show, :update, :destroy, :regenerate] before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update]
def index def index
@applications = current_user.applications.order(id: :desc).page(params[:page]) @applications = current_user.applications.order(id: :desc).page(params[:page])
@ -59,6 +60,12 @@ class Settings::ApplicationsController < Settings::BaseController
end end
def application_params def application_params
params.expect(doorkeeper_application: [:name, :redirect_uri, :website, scopes: []]) params
.expect(doorkeeper_application: [:name, :redirect_uri, :scopes, :website])
end
def prepare_scopes
scopes = application_params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
end end
end end

View file

@ -4,9 +4,14 @@ class Settings::BaseController < ApplicationController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_cache_headers
private private
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def require_not_suspended! def require_not_suspended!
forbidden if current_account.unavailable? forbidden if current_account.unavailable?
end end

View file

@ -8,7 +8,7 @@ module Settings
def destroy def destroy
if valid_picture? if valid_picture?
if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' }) if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303 redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
else else
redirect_to settings_profile_path redirect_to settings_profile_path

View file

@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
def update def update
if UpdateAccountService.new.call(@account, account_params.except(:settings)) if UpdateAccountService.new.call(@account, account_params.except(:settings))
current_user.update!(settings_attributes: account_params[:settings]) current_user.update!(settings_attributes: account_params[:settings])
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg') redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
else else
render :show render :show

View file

@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
def update def update
if UpdateAccountService.new.call(@account, account_params) if UpdateAccountService.new.call(@account, account_params)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg') redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
else else
@account.build_fields @account.build_fields

View file

@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
def update def update
if UpdateAccountService.new.call(@account, account_params) if UpdateAccountService.new.call(@account, account_params)
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id) ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg') redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
else else
render :show render :show

View file

@ -4,6 +4,7 @@ class SeveredRelationshipsController < ApplicationController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_cache_headers
before_action :set_event, only: [:following, :followers] before_action :set_event, only: [:following, :followers]
@ -48,4 +49,8 @@ class SeveredRelationshipsController < ApplicationController
def acct(account) def acct(account)
account.local? ? account.local_username_and_domain : account.acct account.local? ? account.local_username_and_domain : account.acct
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -5,6 +5,7 @@ class StatusesCleanupController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_policy before_action :set_policy
before_action :set_cache_headers
def show; end def show; end
@ -29,4 +30,8 @@ class StatusesCleanupController < ApplicationController
def resource_params def resource_params
params.expect(account_statuses_cleanup_policy: [:enabled, :min_status_age, :keep_direct, :keep_pinned, :keep_polls, :keep_media, :keep_self_fav, :keep_self_bookmark, :keep_self_emoji, :min_favs, :min_reblogs, :min_emojis]) params.expect(account_statuses_cleanup_policy: [:enabled, :min_status_age, :keep_direct, :keep_pinned, :keep_polls, :keep_media, :keep_self_fav, :keep_self_bookmark, :keep_self_emoji, :min_favs, :min_reblogs, :min_emojis])
end end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end end

View file

@ -2,18 +2,11 @@
module Admin::Trends::StatusesHelper module Admin::Trends::StatusesHelper
def one_line_preview(status) def one_line_preview(status)
text = begin text = if status.local?
if status.local?
status.text.split("\n").first status.text.split("\n").first
else else
Nokogiri::HTML5(status.text).css('html > body > *').first&.text Nokogiri::HTML5(status.text).css('html > body > *').first&.text
end end
rescue ArgumentError
# This can happen if one of the Nokogumbo limits is encountered
# Unfortunately, it does not use a more precise error class
# nor allows more graceful handling
''
end
return '' if text.blank? return '' if text.blank?

View file

@ -163,49 +163,24 @@ module JsonLdHelper
end end
end end
# Fetch the resource given by uri. def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
# @param uri [String]
# @param id_is_known [Boolean]
# @param on_behalf_of [nil, Account]
# @param raise_on_error [Symbol<:all, :temporary, :none>] See {#fetch_resource_without_id_validation} for possible values
def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: :none, request_options: {})
unless id_is_known unless id_is_known
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error) json = fetch_resource_without_id_validation(uri, on_behalf_of)
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id']) return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
uri = json['id'] uri = json['id']
end end
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error, request_options: request_options) json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
json.present? && json['id'] == uri ? json : nil json.present? && json['id'] == uri ? json : nil
end end
# Fetch the resource given by uri def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
#
# If an error is raised, it contains the response and can be captured for handling like
#
# begin
# fetch_resource_without_id_validation(uri, nil, true)
# rescue Mastodon::UnexpectedResponseError => e
# e.response
# end
#
# @param uri [String]
# @param on_behalf_of [nil, Account]
# @param raise_on_error [Symbol<:all, :temporary, :none>]
# - +:all+ - raise if response code is not in the 2xx range
# - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404
# (see {#response_error_unsalvageable} )
# - +:none+ - do not raise, return +nil+
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: :none, request_options: {})
on_behalf_of ||= Account.representative on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of, options: request_options).perform do |response| build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && ( raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
raise_on_error == :all ||
(!response_error_unsalvageable?(response) && raise_on_error == :temporary)
)
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response) body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
end end

View file

@ -1,31 +0,0 @@
# frozen_string_literal: true
class DateOfBirthInput < SimpleForm::Inputs::Base
OPTIONS = [
{ autocomplete: 'bday-day', maxlength: 2, pattern: '[0-9]+', placeholder: 'DD' }.freeze,
{ autocomplete: 'bday-month', maxlength: 2, pattern: '[0-9]+', placeholder: 'MM' }.freeze,
{ autocomplete: 'bday-year', maxlength: 4, pattern: '[0-9]+', placeholder: 'YYYY' }.freeze,
].freeze
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
merged_input_options[:inputmode] = 'numeric'
values = (object.public_send(attribute_name) || '').split('.')
safe_join(Array.new(3) do |index|
options = merged_input_options.merge(OPTIONS[index]).merge id: generate_id(index), 'aria-label': I18n.t("simple_form.labels.user.date_of_birth_#{index + 1}i"), value: values[index]
@builder.text_field("#{attribute_name}(#{index + 1}i)", options)
end)
end
def label_target
"#{attribute_name}_1i"
end
private
def generate_id(index)
"#{object_name}_#{attribute_name}_#{index + 1}i"
end
end

View file

@ -1,7 +1,7 @@
import './public-path'; import './public-path';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { afterInitialRender } from 'mastodon/hooks/useRenderSignal'; import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';
import { start } from '../mastodon/common'; import { start } from '../mastodon/common';
import { Status } from '../mastodon/features/standalone/status'; import { Status } from '../mastodon/features/standalone/status';

View file

@ -68,7 +68,7 @@ function loaded() {
if (id) message = localeData[id]; if (id) message = localeData[id];
message ??= defaultMessage as string; if (!message) message = defaultMessage as string;
const messageFormat = new IntlMessageFormat(message, locale); const messageFormat = new IntlMessageFormat(message, locale);
return messageFormat.format(values) as string; return messageFormat.format(values) as string;

View file

@ -14,9 +14,6 @@ const isHashtagClick = (element: HTMLAnchorElement) =>
element.textContent?.[0] === '#' || element.textContent?.[0] === '#' ||
element.previousSibling?.textContent?.endsWith('#'); element.previousSibling?.textContent?.endsWith('#');
const isFeaturedHashtagClick = (element: HTMLAnchorElement) =>
isHashtagClick(element) && element.href.includes('/tagged/');
export const useLinks = () => { export const useLinks = () => {
const history = useHistory(); const history = useHistory();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -32,19 +29,6 @@ export const useLinks = () => {
[history], [history],
); );
const handleFeaturedHashtagClick = useCallback(
(element: HTMLAnchorElement) => {
const { textContent, href } = element;
if (!textContent) return;
const url = new URL(href);
history.push(url.pathname);
},
[history],
);
const handleMentionClick = useCallback( const handleMentionClick = useCallback(
async (element: HTMLAnchorElement) => { async (element: HTMLAnchorElement) => {
const result = await dispatch(openURL({ url: element.href })); const result = await dispatch(openURL({ url: element.href }));
@ -77,15 +61,12 @@ export const useLinks = () => {
if (isMentionClick(target)) { if (isMentionClick(target)) {
e.preventDefault(); e.preventDefault();
void handleMentionClick(target); void handleMentionClick(target);
} else if (isFeaturedHashtagClick(target)) {
e.preventDefault();
handleFeaturedHashtagClick(target);
} else if (isHashtagClick(target)) { } else if (isHashtagClick(target)) {
e.preventDefault(); e.preventDefault();
handleHashtagClick(target); handleHashtagClick(target);
} }
}, },
[handleMentionClick, handleFeaturedHashtagClick, handleHashtagClick], [handleMentionClick, handleHashtagClick],
); );
return handleClick; return handleClick;

BIN
app/javascript/icons/android-chrome-144x144.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-192x192.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-256x256.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-36x36.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-384x384.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-48x48.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-512x512.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-72x72.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

BIN
app/javascript/icons/android-chrome-96x96.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Before After
Before After

BIN
app/javascript/icons/apple-touch-icon-1024x1024.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

BIN
app/javascript/icons/apple-touch-icon-114x114.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before After
Before After

BIN
app/javascript/icons/apple-touch-icon-120x120.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before After
Before After

BIN
app/javascript/icons/apple-touch-icon-144x144.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

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