Merge tag 'v3.5.3'

This commit is contained in:
bgme 2022-05-27 10:10:14 +08:00
commit 86feff686d
292 changed files with 11461 additions and 3968 deletions

View file

@ -109,7 +109,8 @@ class Account < ApplicationRecord
scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) }
scope :without_unapproved, -> { left_outer_joins(:user).remote.or(left_outer_joins(:user).merge(User.approved.confirmed)) }
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 :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')) }
@ -193,7 +194,7 @@ class Account < ApplicationRecord
end
def searchable?
!(suspended? || moved?)
!(suspended? || moved?) && (!local? || (approved? && confirmed?))
end
def possibly_stale?
@ -461,9 +462,11 @@ class Account < ApplicationRecord
accounts.*,
ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
FROM accounts
LEFT JOIN users ON accounts.id = users.account_id
WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
AND accounts.suspended_at IS NULL
AND accounts.moved_to_account_id IS NULL
AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
ORDER BY rank DESC
LIMIT :limit OFFSET :offset
SQL
@ -539,9 +542,11 @@ class Account < ApplicationRecord
(count(f.id) + 1) * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
FROM accounts
LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id) OR (accounts.id = f.target_account_id AND f.account_id = :id)
LEFT JOIN users ON accounts.id = users.account_id
WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
AND accounts.suspended_at IS NULL
AND accounts.moved_to_account_id IS NULL
AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
GROUP BY accounts.id
ORDER BY rank DESC
LIMIT :limit OFFSET :offset

View file

@ -15,6 +15,7 @@
class AccountMigration < ApplicationRecord
include Redisable
include Lockable
COOLDOWN_PERIOD = 30.days.freeze
@ -41,12 +42,8 @@ class AccountMigration < ApplicationRecord
return false unless errors.empty?
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
save
else
raise Mastodon::RaceConditionError
end
with_lock("account_migration:#{account.id}") do
save
end
end
@ -83,8 +80,4 @@ class AccountMigration < ApplicationRecord
def validate_migration_cooldown
errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
end
def lock_options
{ redis: redis, key: "account_migration:#{account.id}" }
end
end

View file

@ -20,4 +20,16 @@ class AccountStat < ApplicationRecord
belongs_to :account, inverse_of: :account_stat
update_index('accounts', :account)
def following_count
[attributes['following_count'], 0].max
end
def followers_count
[attributes['followers_count'], 0].max
end
def statuses_count
[attributes['statuses_count'], 0].max
end
end

View file

@ -92,6 +92,10 @@ class Admin::AccountAction
text: text_for_warning,
status_ids: status_ids
)
# A log entry is only interesting if the warning contains
# custom text from someone. Otherwise it's just noise.
log_action(:create, @warning) if @warning.text.present? && type == 'none'
end
def process_reports!
@ -160,8 +164,8 @@ class Admin::AccountAction
def reports
@reports ||= begin
if type == 'none' && with_report?
[report]
if type == 'none'
with_report? ? [report] : []
else
Report.where(target_account: target_account).unresolved
end

View file

@ -103,7 +103,7 @@ class Admin::StatusBatchAction
def handle_report!
@report = Report.new(report_params) unless with_report?
@report.status_ids = (@report.status_ids + status_ids.map(&:to_i)).uniq
@report.status_ids = (@report.status_ids + allowed_status_ids).uniq
@report.save!
@report_id = @report.id
@ -135,4 +135,8 @@ class Admin::StatusBatchAction
def report_params
{ account: current_account, target_account: target_account }
end
def allowed_status_ids
AccountStatusesFilter.new(@report.target_account, current_account).results.with_discarded.where(id: status_ids).pluck(:id)
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Lockable
# @param [String] lock_name
# @param [ActiveSupport::Duration] autorelease Automatically release the lock after this time
# @param [Boolean] raise_on_failure Raise an error if a lock cannot be acquired, or fail silently
# @raise [Mastodon::RaceConditionError]
def with_lock(lock_name, autorelease: 15.minutes, raise_on_failure: true)
with_redis do |redis|
RedisLock.acquire(redis: redis, key: "lock:#{lock_name}", autorelease: autorelease.seconds) do |lock|
if lock.acquired?
yield
elsif raise_on_failure
raise Mastodon::RaceConditionError, "Could not acquire lock for #{lock_name}, try again later"
end
end
end
end
end

View file

@ -1,11 +1,11 @@
# frozen_string_literal: true
module Redisable
extend ActiveSupport::Concern
private
def redis
Thread.current[:redis] ||= RedisConfiguration.pool.checkout
end
def with_redis(&block)
RedisConfiguration.with(&block)
end
end

View file

@ -39,13 +39,12 @@ class Poll < ApplicationRecord
before_validation :prepare_options, if: :local?
before_validation :prepare_votes_count
after_initialize :prepare_cached_tallies
before_validation :prepare_cached_tallies
after_commit :reset_parent_cache, on: :update
def loaded_options
options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? cached_tallies[key] : nil) }
options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? (cached_tallies[key] || 0) : nil) }
end
def possibly_stale?

View file

@ -454,7 +454,7 @@ class Status < ApplicationRecord
end
def set_poll_id
update_column(:poll_id, poll.id) unless poll.nil?
update_column(:poll_id, poll.id) if association(:poll).loaded? && poll.present?
end
def set_visibility

View file

@ -17,6 +17,18 @@ class StatusStat < ApplicationRecord
after_commit :reset_parent_cache
def replies_count
[attributes['replies_count'], 0].max
end
def reblogs_count
[attributes['reblogs_count'], 0].max
end
def favourites_count
[attributes['favourites_count'], 0].max
end
private
def reset_parent_cache

View file

@ -11,11 +11,11 @@ class Trends::History
end
def uses
redis.mget(*@days.map { |day| day.key_for(:uses) }).map(&:to_i).sum
with_redis { |redis| redis.mget(*@days.map { |day| day.key_for(:uses) }).map(&:to_i).sum }
end
def accounts
redis.pfcount(*@days.map { |day| day.key_for(:accounts) })
with_redis { |redis| redis.pfcount(*@days.map { |day| day.key_for(:accounts) }) }
end
end
@ -33,19 +33,21 @@ class Trends::History
attr_reader :day
def accounts
redis.pfcount(key_for(:accounts))
with_redis { |redis| redis.pfcount(key_for(:accounts)) }
end
def uses
redis.get(key_for(:uses))&.to_i || 0
with_redis { |redis| redis.get(key_for(:uses))&.to_i || 0 }
end
def add(account_id)
redis.pipelined do
redis.incrby(key_for(:uses), 1)
redis.pfadd(key_for(:accounts), account_id)
redis.expire(key_for(:uses), EXPIRE_AFTER)
redis.expire(key_for(:accounts), EXPIRE_AFTER)
with_redis do |redis|
redis.pipelined do |pipeline|
pipeline.incrby(key_for(:uses), 1)
pipeline.pfadd(key_for(:accounts), account_id)
pipeline.expire(key_for(:uses), EXPIRE_AFTER)
pipeline.expire(key_for(:accounts), EXPIRE_AFTER)
end
end
end

View file

@ -53,6 +53,7 @@ class User < ApplicationRecord
include Settings::Extend
include UserRoles
include Redisable
include LanguagesHelper
# The home and list feeds will be stored in Redis for this amount
# of time, and status fan-out to followers will include only people
@ -248,7 +249,7 @@ class User < ApplicationRecord
end
def preferred_posting_language
settings.default_language || locale
valid_locale_cascade(settings.default_language, locale)
end
def setting_default_privacy