Merge tag 'v4.1.10' into chinwag-4.1

This commit is contained in:
Mike Barnes 2023-10-16 13:16:30 +11:00
commit 9d4892ca17
36 changed files with 181 additions and 170 deletions

View file

@ -19,10 +19,8 @@ jobs:
ghcr.io/mastodon/mastodon ghcr.io/mastodon/mastodon
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages # Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages
cache: false cache: false
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: | flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }} latest=false
tags: | tags: |
type=pep440,pattern={{raw}} type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}} type=pep440,pattern=v{{major}}.{{minor}}

View file

@ -1,41 +0,0 @@
name: Ruby Linting
on:
push:
branches-ignore:
- 'dependabot/**'
paths:
- 'Gemfile*'
- '.rubocop.yml'
- '**/*.rb'
- '**/*.rake'
- '.github/workflows/lint-ruby.yml'
pull_request:
paths:
- 'Gemfile*'
- '.rubocop.yml'
- '**/*.rb'
- '**/*.rake'
- '.github/workflows/lint-ruby.yml'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set-up RuboCop Problem Mathcher
uses: r7kamura/rubocop-problem-matchers-action@v1
- name: Run rubocop
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LINTER_RULES_PATH: .
RUBY_CONFIG_FILE: .rubocop.yml
VALIDATE_ALL_CODEBASE: false
VALIDATE_RUBY: true

View file

@ -3,6 +3,28 @@ Changelog
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.1.10] - 2023-10-10
### Changed
- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246))
- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200))
### Fixed
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253))
- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258))
- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186))
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
## [4.1.9] - 2023-09-20
### Fixed
- Fix post translation erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26990))
## [4.1.8] - 2023-09-19 ## [4.1.8] - 2023-09-19
### Fixed ### Fixed

View file

@ -10,40 +10,40 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (6.1.7.4) actioncable (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.1.7.4) actionmailbox (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
activejob (= 6.1.7.4) activejob (= 6.1.7.6)
activerecord (= 6.1.7.4) activerecord (= 6.1.7.6)
activestorage (= 6.1.7.4) activestorage (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.1.7.4) actionmailer (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
actionview (= 6.1.7.4) actionview (= 6.1.7.6)
activejob (= 6.1.7.4) activejob (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.1.7.4) actionpack (6.1.7.6)
actionview (= 6.1.7.4) actionview (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
rack (~> 2.0, >= 2.0.9) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7.4) actiontext (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
activerecord (= 6.1.7.4) activerecord (= 6.1.7.6)
activestorage (= 6.1.7.4) activestorage (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.1.7.4) actionview (6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -54,22 +54,22 @@ GEM
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8) active_record_query_trace (1.8)
activejob (6.1.7.4) activejob (6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.1.7.4) activemodel (6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
activerecord (6.1.7.4) activerecord (6.1.7.6)
activemodel (= 6.1.7.4) activemodel (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
activestorage (6.1.7.4) activestorage (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
activejob (= 6.1.7.4) activejob (= 6.1.7.6)
activerecord (= 6.1.7.4) activerecord (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (6.1.7.4) activesupport (6.1.7.6)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -404,13 +404,13 @@ GEM
mime-types (3.4.1) mime-types (3.4.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105) mime-types-data (3.2022.0105)
mini_mime (1.1.2) mini_mime (1.1.5)
mini_portile2 (2.8.2) mini_portile2 (2.8.4)
minitest (5.17.0) minitest (5.17.0)
msgpack (1.6.0) msgpack (1.6.0)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.1.1) multipart-post (2.1.1)
net-imap (0.3.6) net-imap (0.3.7)
date date
net-protocol net-protocol
net-ldap (0.17.1) net-ldap (0.17.1)
@ -491,13 +491,13 @@ GEM
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (5.0.1) public_suffix (5.0.1)
puma (5.6.5) puma (5.6.7)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.0) pundit (2.3.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.6.2) racc (1.6.2)
rack (2.2.7) rack (2.2.8)
rack-attack (6.6.1) rack-attack (6.6.1)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-cors (1.1.1) rack-cors (1.1.1)
@ -512,20 +512,20 @@ GEM
rack rack
rack-test (2.0.2) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails (6.1.7.4) rails (6.1.7.6)
actioncable (= 6.1.7.4) actioncable (= 6.1.7.6)
actionmailbox (= 6.1.7.4) actionmailbox (= 6.1.7.6)
actionmailer (= 6.1.7.4) actionmailer (= 6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
actiontext (= 6.1.7.4) actiontext (= 6.1.7.6)
actionview (= 6.1.7.4) actionview (= 6.1.7.6)
activejob (= 6.1.7.4) activejob (= 6.1.7.6)
activemodel (= 6.1.7.4) activemodel (= 6.1.7.6)
activerecord (= 6.1.7.4) activerecord (= 6.1.7.6)
activestorage (= 6.1.7.4) activestorage (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 6.1.7.4) railties (= 6.1.7.6)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -541,9 +541,9 @@ GEM
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6) rails-settings-cached (0.6.6)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (6.1.7.4) railties (6.1.7.6)
actionpack (= 6.1.7.4) actionpack (= 6.1.7.6)
activesupport (= 6.1.7.4) activesupport (= 6.1.7.6)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
@ -634,7 +634,7 @@ GEM
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
semantic_range (3.0.0) semantic_range (3.0.0)
sidekiq (6.5.8) sidekiq (6.5.11)
connection_pool (>= 2.2.5, < 3) connection_pool (>= 2.2.5, < 3)
rack (~> 2.0) rack (~> 2.0)
redis (>= 4.5.0, < 5) redis (>= 4.5.0, < 5)
@ -746,14 +746,14 @@ GEM
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
websocket-driver (0.7.5) websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
wisper (2.0.1) wisper (2.0.1)
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.8) zeitwerk (2.6.12)
PLATFORMS PLATFORMS
ruby ruby

View file

@ -34,7 +34,9 @@ class Importer::BaseImporter
# Estimate the amount of documents that would be indexed. Not exact! # Estimate the amount of documents that would be indexed. Not exact!
# @returns [Integer] # @returns [Integer]
def estimate! def estimate!
ActiveRecord::Base.connection_pool.with_connection { |connection| connection.select_one("SELECT reltuples AS estimate FROM pg_class WHERE relname = '#{index.adapter.target.table_name}'")['estimate'].to_i } reltuples = ActiveRecord::Base.connection_pool.with_connection { |connection| connection.select_one("SELECT reltuples FROM pg_class WHERE relname = '#{index.adapter.target.table_name}'")['reltuples'].to_i }
# If the table has never yet been vacuumed or analyzed, reltuples contains -1
[reltuples, 0].max
end end
# Import data from the database into the index # Import data from the database into the index

View file

@ -46,7 +46,7 @@ class TranslationService::DeepL < TranslationService
raise UnexpectedResponseError unless json.is_a?(Hash) raise UnexpectedResponseError unless json.is_a?(Hash)
Translation.new(text: json.dig('translations', 0, 'text'), detected_source_language: json.dig('translations', 0, 'detected_source_language')&.downcase, provider: 'DeepL.com') Translation.new(text: Sanitize.fragment(json.dig('translations', 0, 'text'), Sanitize::Config::MASTODON_STRICT), detected_source_language: json.dig('translations', 0, 'detected_source_language')&.downcase, provider: 'DeepL.com')
rescue Oj::ParseError rescue Oj::ParseError
raise UnexpectedResponseError raise UnexpectedResponseError
end end

View file

@ -37,7 +37,7 @@ class TranslationService::LibreTranslate < TranslationService
raise UnexpectedResponseError unless json.is_a?(Hash) raise UnexpectedResponseError unless json.is_a?(Hash)
Translation.new(text: json['translatedText'], detected_source_language: source_language, provider: 'LibreTranslate') Translation.new(text: Sanitize.fragment(json['translatedText'], Sanitize::Config::MASTODON_STRICT), detected_source_language: source_language, provider: 'LibreTranslate')
rescue Oj::ParseError rescue Oj::ParseError
raise UnexpectedResponseError raise UnexpectedResponseError
end end

View file

@ -61,9 +61,9 @@ class Account < ApplicationRecord
trust_level trust_level
) )
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/ URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
include Attachmentable include Attachmentable
@ -114,8 +114,8 @@ class Account < ApplicationRecord
scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) } scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) } scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) } scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) } scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) }
scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) } scope :by_recent_sign_in, -> { order(Arel.sql('users.current_sign_in_at DESC NULLS LAST')) }
scope :popular, -> { order('account_stats.followers_count desc') } scope :popular, -> { order('account_stats.followers_count desc') }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) } scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) } scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }

View file

@ -38,7 +38,7 @@ class Admin::ActionLogFilter
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze, destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
destroy_user_role: { target_type: 'UserRole', action: 'destroy' }.freeze, destroy_user_role: { target_type: 'UserRole', action: 'destroy' }.freeze,
destroy_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'destroy' }.freeze, destroy_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'destroy' }.freeze,
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze, disable_2fa_user: { target_type: 'User', action: 'disable_2fa' }.freeze,
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze, disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
disable_user: { target_type: 'User', action: 'disable' }.freeze, disable_user: { target_type: 'User', action: 'disable' }.freeze,
enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze, enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze,

View file

@ -18,7 +18,7 @@ module AccountAvatar
included do included do
# Avatar upload # Avatar upload
has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail] has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
validates_attachment_size :avatar, less_than: LIMIT validates_attachment_size :avatar, less_than: LIMIT
remotable_attachment :avatar, LIMIT, suppress_errors: false remotable_attachment :avatar, LIMIT, suppress_errors: false

View file

@ -19,7 +19,7 @@ module AccountHeader
included do included do
# Header upload # Header upload
has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '+profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail] has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
validates_attachment_size :header, less_than: LIMIT validates_attachment_size :header, less_than: LIMIT
remotable_attachment :header, LIMIT, suppress_errors: false remotable_attachment :header, LIMIT, suppress_errors: false

View file

@ -37,7 +37,7 @@ class CustomEmoji < ApplicationRecord
belongs_to :category, class_name: 'CustomEmojiCategory', optional: true belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode
has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' } }, validate_media_type: false
before_validation :downcase_domain before_validation :downcase_domain

View file

@ -169,7 +169,7 @@ class MediaAttachment < ApplicationRecord
}.freeze }.freeze
GLOBAL_CONVERT_OPTIONS = { GLOBAL_CONVERT_OPTIONS = {
all: '-quality 90 +profile "!icc,*" +set modify-date +set create-date', all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp',
}.freeze }.freeze
belongs_to :account, inverse_of: :media_attachments, optional: true belongs_to :account, inverse_of: :media_attachments, optional: true

View file

@ -50,7 +50,7 @@ class PreviewCard < ApplicationRecord
has_and_belongs_to_many :statuses has_and_belongs_to_many :statuses
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set modify-date +set create-date' }, validate_media_type: false has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false
validates :url, presence: true, uniqueness: true validates :url, presence: true, uniqueness: true
validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES

View file

@ -25,7 +25,7 @@ class PreviewCardProvider < ApplicationRecord
validates :domain, presence: true, uniqueness: true, domain: true validates :domain, presence: true, uniqueness: true, domain: true
has_attached_file :icon, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false has_attached_file :icon, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' } }, validate_media_type: false
validates_attachment :icon, content_type: { content_type: ICON_MIME_TYPES }, size: { less_than: LIMIT } validates_attachment :icon, content_type: { content_type: ICON_MIME_TYPES }, size: { less_than: LIMIT }
remotable_attachment :icon, LIMIT remotable_attachment :icon, LIMIT

View file

@ -60,13 +60,13 @@ class RelationshipFilter
def relationship_scope(value) def relationship_scope(value)
case value case value
when 'following' when 'following'
account.following.eager_load(:account_stat).reorder(nil) account.following.includes(:account_stat).reorder(nil)
when 'followed_by' when 'followed_by'
account.followers.eager_load(:account_stat).reorder(nil) account.followers.includes(:account_stat).reorder(nil)
when 'mutual' when 'mutual'
account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following)) account.followers.includes(:account_stat).reorder(nil).merge(Account.where(id: account.following))
when 'invited' when 'invited'
Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil) Account.joins(user: :invite).merge(Invite.where(user: account.user)).includes(:account_stat).reorder(nil)
else else
raise Mastodon::InvalidParameterError, "Unknown relationship: #{value}" raise Mastodon::InvalidParameterError, "Unknown relationship: #{value}"
end end
@ -112,7 +112,7 @@ class RelationshipFilter
def activity_scope(value) def activity_scope(value)
case value case value
when 'dormant' when 'dormant'
AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago))) Account.joins(:account_stat).where(account_stat: { last_status_at: [nil, ...1.month.ago] })
else else
raise Mastodon::InvalidParameterError, "Unknown activity: #{value}" raise Mastodon::InvalidParameterError, "Unknown activity: #{value}"
end end

View file

@ -40,7 +40,7 @@ class SiteUpload < ApplicationRecord
mascot: {}.freeze, mascot: {}.freeze,
}.freeze }.freeze
has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector] has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/ validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/
validates :file, presence: true validates :file, presence: true

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class BackupPolicy < ApplicationPolicy class BackupPolicy < ApplicationPolicy
MIN_AGE = 1.week MIN_AGE = 6.days
def create? def create?
user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero? user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?

View file

@ -12,8 +12,8 @@ class TranslateStatusService < BaseService
@content = status_content_format(@status) @content = status_content_format(@status)
@target_language = target_language @target_language = target_language
Rails.cache.fetch("translations/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do Rails.cache.fetch("translations:v2/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do
Sanitize.fragment(translation_backend.translate(@content, @status.language, @target_language), Sanitize::Config::MASTODON_STRICT) translation_backend.translate(@content, @status.language, @target_language)
end end
end end

View file

@ -3,7 +3,7 @@
class AccountDeletionWorker class AccountDeletionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
def perform(account_id, options = {}) def perform(account_id, options = {})
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true) reserve_username = options.with_indifferent_access.fetch(:reserve_username, true)

View file

@ -3,7 +3,7 @@
class ActivityPub::SynchronizeFeaturedCollectionWorker class ActivityPub::SynchronizeFeaturedCollectionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
def perform(account_id, options = {}) def perform(account_id, options = {})
options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys) options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys)

View file

@ -3,7 +3,7 @@
class ActivityPub::SynchronizeFeaturedTagsCollectionWorker class ActivityPub::SynchronizeFeaturedTagsCollectionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
def perform(account_id, url) def perform(account_id, url)
ActivityPub::FetchFeaturedTagsCollectionService.new.call(Account.find(account_id), url) ActivityPub::FetchFeaturedTagsCollectionService.new.call(Account.find(account_id), url)

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
sidekiq_options queue: 'push', lock: :until_executed sidekiq_options queue: 'push', lock: :until_executed, lock_ttl: 1.day.to_i
# Distribute an profile update to servers that might have a copy # Distribute an profile update to servers that might have a copy
# of the account in question # of the account in question

View file

@ -3,7 +3,7 @@
class Admin::AccountDeletionWorker class Admin::AccountDeletionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
def perform(account_id) def perform(account_id)
DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true) DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true)

View file

@ -3,7 +3,7 @@
class Admin::DomainPurgeWorker class Admin::DomainPurgeWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
def perform(domain) def perform(domain)
PurgeDomainService.new.call(domain) PurgeDomainService.new.call(domain)

View file

@ -3,7 +3,7 @@
class PublishScheduledStatusWorker class PublishScheduledStatusWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options lock: :until_executed sidekiq_options lock: :until_executed, lock_ttl: 1.hour.to_i
def perform(scheduled_status_id) def perform(scheduled_status_id)
scheduled_status = ScheduledStatus.find(scheduled_status_id) scheduled_status = ScheduledStatus.find(scheduled_status_id)

View file

@ -3,7 +3,7 @@
class ResolveAccountWorker class ResolveAccountWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
def perform(uri) def perform(uri)
ResolveAccountService.new.call(uri) ResolveAccountService.new.call(uri)

View file

@ -4,7 +4,7 @@ class Scheduler::IndexingScheduler
include Sidekiq::Worker include Sidekiq::Worker
include Redisable include Redisable
sidekiq_options retry: 0 sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 30.minutes.to_i
IMPORT_BATCH_SIZE = 1000 IMPORT_BATCH_SIZE = 1000
SCAN_BATCH_SIZE = 10 * IMPORT_BATCH_SIZE SCAN_BATCH_SIZE = 10 * IMPORT_BATCH_SIZE

View file

@ -3,7 +3,7 @@
class Scheduler::ScheduledStatusesScheduler class Scheduler::ScheduledStatusesScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options retry: 0 sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.hour.to_i
def perform def perform
publish_scheduled_statuses! publish_scheduled_statuses!

View file

@ -3,7 +3,7 @@
class Scheduler::Trends::RefreshScheduler class Scheduler::Trends::RefreshScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options retry: 0 sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 30.minutes.to_i
def perform def perform
Trends.refresh! Trends.refresh!

View file

@ -3,7 +3,7 @@
class VerifyAccountLinksWorker class VerifyAccountLinksWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'default', retry: false, lock: :until_executed sidekiq_options queue: 'default', retry: false, lock: :until_executed, lock_ttl: 1.hour.to_i
def perform(account_id) def perform(account_id)
account = Account.find(account_id) account = Account.find(account_id)

View file

@ -56,7 +56,7 @@ services:
web: web:
build: . build: .
image: ghcr.io/mastodon/mastodon:v4.1.8 image: ghcr.io/mastodon/mastodon:v4.1.10
restart: always restart: always
env_file: .env.production env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000" command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
@ -77,7 +77,7 @@ services:
streaming: streaming:
build: . build: .
image: ghcr.io/mastodon/mastodon:v4.1.8 image: ghcr.io/mastodon/mastodon:v4.1.10
restart: always restart: always
env_file: .env.production env_file: .env.production
command: node ./streaming command: node ./streaming
@ -95,7 +95,7 @@ services:
sidekiq: sidekiq:
build: . build: .
image: ghcr.io/mastodon/mastodon:v4.1.8 image: ghcr.io/mastodon/mastodon:v4.1.10
restart: always restart: always
env_file: .env.production env_file: .env.production
command: bundle exec sidekiq command: bundle exec sidekiq

View file

@ -13,7 +13,7 @@ module Mastodon
end end
def patch def patch
8 10
end end
def flags def flags

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
Fabricator(:account_stat) do Fabricator(:account_stat) do
account nil account { Fabricate.build(:account) }
statuses_count "" statuses_count '123'
following_count "" following_count '456'
followers_count "" followers_count '789'
end end

View file

@ -695,7 +695,7 @@ RSpec.describe Account, type: :model do
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
end end
xit 'does not match URL querystring' do it 'does not match URL query string' do
expect(subject.match('https://example.com/?x=@alice')).to be_nil expect(subject.match('https://example.com/?x=@alice')).to be_nil
end end
end end

View file

@ -6,32 +6,60 @@ describe RelationshipFilter do
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
describe '#results' do describe '#results' do
context 'when default params are used' do let(:account_of_7_months) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 7.months.ago).account }
let(:subject) do let(:account_of_1_day) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 1.day.ago).account }
RelationshipFilter.new(account, 'order' => 'active').results let(:account_of_3_days) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 3.days.ago).account }
let(:silent_account) { Fabricate(:account_stat, statuses_count: 0, last_status_at: nil).account }
before do
account.follow!(account_of_7_months)
account.follow!(account_of_1_day)
account.follow!(account_of_3_days)
account.follow!(silent_account)
end
context 'when ordering by last activity' do
context 'when not filtering' do
subject do
described_class.new(account, 'order' => 'active').results
end
it 'returns followings ordered by last activity' do
expect(subject).to eq [account_of_1_day, account_of_3_days, account_of_7_months, silent_account]
end
end end
before do context 'when filtering for dormant accounts' do
add_following_account_with(last_status_at: 7.days.ago) subject do
add_following_account_with(last_status_at: 1.day.ago) described_class.new(account, 'order' => 'active', 'activity' => 'dormant').results
add_following_account_with(last_status_at: 3.days.ago) end
it 'returns dormant followings ordered by last activity' do
expect(subject).to eq [account_of_7_months, silent_account]
end
end
end
context 'when ordering by account creation' do
context 'when not filtering' do
subject do
described_class.new(account, 'order' => 'recent').results
end
it 'returns followings ordered by last account creation' do
expect(subject).to eq [silent_account, account_of_3_days, account_of_1_day, account_of_7_months]
end
end end
it 'returns followings ordered by last activity' do context 'when filtering for dormant accounts' do
expected_result = account.following.eager_load(:account_stat).reorder(nil).by_recent_status subject do
described_class.new(account, 'order' => 'recent', 'activity' => 'dormant').results
end
expect(subject).to eq expected_result it 'returns dormant followings ordered by last activity' do
expect(subject).to eq [silent_account, account_of_7_months]
end
end end
end end
end end
def add_following_account_with(last_status_at:)
following_account = Fabricate(:account)
Fabricate(:account_stat, account: following_account,
last_status_at: last_status_at,
statuses_count: 1,
following_count: 0,
followers_count: 0)
Fabricate(:follow, account: account, target_account: following_account).account
end
end end