Merge remote-tracking branch 'parent/main' into upstream-20241119

This commit is contained in:
KMY 2024-11-19 08:49:55 +09:00
commit 055045981f
221 changed files with 2006 additions and 1127 deletions

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
primary_abstract_class
include Remotable

View file

@ -4,95 +4,85 @@ module Account::Associations
extend ActiveSupport::Concern
included do
# Local users
has_one :user, inverse_of: :account, dependent: :destroy
# Core associations
with_options dependent: :destroy do
# Association where account owns record
with_options inverse_of: :account do
has_many :account_moderation_notes
has_many :account_pins
has_many :account_warnings
has_many :aliases, class_name: 'AccountAlias'
has_many :antenna_accounts
has_many :antennas
has_many :bookmarks
has_many :bookmark_categories
has_many :circle_accounts
has_many :circles
has_many :conversations, class_name: 'AccountConversation'
has_many :custom_filters
has_many :emoji_reactions
has_many :favourites
has_many :featured_tags, -> { includes(:tag) }
has_many :list_accounts
has_many :media_attachments
has_many :mentions
has_many :migrations, class_name: 'AccountMigration'
has_many :ng_rule_histories
has_many :notification_permissions
has_many :notification_requests
has_many :notifications
has_many :owned_lists, class_name: 'List'
has_many :polls
has_many :report_notes
has_many :reports
has_many :scheduled_statuses
has_many :scheduled_expiration_statuses
has_many :status_pins
has_many :statuses
# Timelines
has_many :statuses, inverse_of: :account, dependent: :destroy
has_many :favourites, inverse_of: :account, dependent: :destroy
has_many :emoji_reactions, inverse_of: :account, dependent: :destroy
has_many :bookmarks, inverse_of: :account, dependent: :destroy
has_many :bookmark_categories, inverse_of: :account, dependent: :destroy
has_many :circles, inverse_of: :account, dependent: :destroy
has_many :antennas, inverse_of: :account, dependent: :destroy
has_many :mentions, inverse_of: :account, dependent: :destroy
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy
has_many :scheduled_expiration_statuses, inverse_of: :account, dependent: :destroy
has_many :ng_rule_histories, inverse_of: :account, dependent: :destroy
has_one :deletion_request, class_name: 'AccountDeletionRequest'
has_one :follow_recommendation_suppression
has_one :notification_policy
has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy'
has_one :user
end
# Notifications
has_many :notifications, inverse_of: :account, dependent: :destroy
has_one :notification_policy, inverse_of: :account, dependent: :destroy
has_many :notification_permissions, inverse_of: :account, dependent: :destroy
has_many :notification_requests, inverse_of: :account, dependent: :destroy
# Association where account is targeted by record
with_options foreign_key: :target_account_id, inverse_of: :target_account do
has_many :strikes, class_name: 'AccountWarning'
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote'
has_many :targeted_reports, class_name: 'Report'
end
end
# Pinned statuses
has_many :status_pins, inverse_of: :account, dependent: :destroy
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
# Status records pinned by the account
has_many :pinned_statuses, -> { reorder(status_pins: { created_at: :desc }) }, through: :status_pins, class_name: 'Status', source: :status
# Endorsements
has_many :account_pins, inverse_of: :account, dependent: :destroy
# Account records endorsed (pinned) by the account
has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account
# Media
has_many :media_attachments, dependent: :destroy
has_many :polls, dependent: :destroy
# Report relationships
has_many :reports, dependent: :destroy, inverse_of: :account
has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
has_many :report_notes, dependent: :destroy
has_many :custom_filters, inverse_of: :account, dependent: :destroy
has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
# Moderation notes
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
has_many :account_warnings, dependent: :destroy, inverse_of: :account
has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
# Remote pendings
has_many :pending_follow_requests, dependent: :destroy
has_many :pending_statuses, dependent: :destroy
has_many :fetchable_pending_statuses, class_name: 'PendingStatus', foreign_key: :fetch_account_id, dependent: :destroy, inverse_of: :fetch_account
# Antennas (that the account is on, not owned by the account)
has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
has_many :joined_antennas, class_name: 'Antenna', through: :antenna_accounts, source: :antenna
# Circles (that the account is on, not owned by the account)
has_many :circle_accounts, inverse_of: :account, dependent: :destroy
has_many :joined_circles, class_name: 'Circle', through: :circle_accounts, source: :circle
# Lists (that the account is on, not owned by the account)
has_many :list_accounts, inverse_of: :account, dependent: :destroy
# List records the account has been added to (not owned by the account)
has_many :lists, through: :list_accounts
# Lists (owned by the account)
has_many :owned_lists, class_name: 'List', dependent: :destroy, inverse_of: :account
# Account list items
has_many :joined_antennas, class_name: 'Antenna', through: :antenna_accounts, source: :antenna
has_many :joined_circles, class_name: 'Circle', through: :circle_accounts, source: :circle
# Account migrations
# Account record where account has been migrated
belongs_to :moved_to_account, class_name: 'Account', optional: true
has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
# Hashtags
# Tag records applied to account
has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
# Account deletion requests
has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
# Follow recommendations
# FollowRecommendation for account (surfaced via view)
has_one :follow_recommendation, inverse_of: :account, dependent: nil
has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy
# Account statuses cleanup policy
has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy', inverse_of: :account, dependent: :destroy
# Imports
# BulkImport records owned by account
has_many :bulk_imports, inverse_of: :account, dependent: :delete_all
end
end

View file

@ -80,8 +80,8 @@ module Account::Interactions
has_many :passive_relationships, foreign_key: 'target_account_id', inverse_of: :target_account
end
has_many :following, -> { order('follows.id desc') }, through: :active_relationships, source: :target_account
has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
has_many :following, -> { order(follows: { id: :desc }) }, through: :active_relationships, source: :target_account
has_many :followers, -> { order(follows: { id: :desc }) }, through: :passive_relationships, source: :account
with_options class_name: 'SeveredRelationship', dependent: :destroy do
has_many :severed_relationships, foreign_key: 'local_account_id', inverse_of: :local_account
@ -96,16 +96,16 @@ module Account::Interactions
has_many :block_relationships, foreign_key: 'account_id', inverse_of: :account
has_many :blocked_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
end
has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
has_many :blocking, -> { order(blocks: { id: :desc }) }, through: :block_relationships, source: :target_account
has_many :blocked_by, -> { order(blocks: { id: :desc }) }, through: :blocked_by_relationships, source: :account
# Mute relationships
with_options class_name: 'Mute', dependent: :destroy do
has_many :mute_relationships, foreign_key: 'account_id', inverse_of: :account
has_many :muted_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
end
has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
has_many :muting, -> { order(mutes: { id: :desc }) }, through: :mute_relationships, source: :target_account
has_many :muted_by, -> { order(mutes: { id: :desc }) }, through: :muted_by_relationships, source: :account
has_many :conversation_mutes, dependent: :destroy
has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
has_many :announcement_mutes, dependent: :destroy

View file

@ -28,9 +28,9 @@ class InstanceFilter
def scope_for(key, value)
case key.to_s
when 'limited'
Instance.joins(:domain_block).reorder(Arel.sql('domain_blocks.id desc'))
Instance.joins(:domain_block).reorder(domain_blocks: { id: :desc })
when 'allowed'
Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
Instance.joins(:domain_allow).reorder(domain_allows: { id: :desc })
when 'by_domain'
Instance.matches_domain(value)
when 'availability'

View file

@ -38,7 +38,7 @@ class List < ApplicationRecord
private
def validate_account_lists_limit
errors.add(:base, I18n.t('lists.errors.limit')) if account.lists.count >= PER_ACCOUNT_LIMIT
errors.add(:base, I18n.t('lists.errors.limit')) if account.owned_lists.count >= PER_ACCOUNT_LIMIT
end
def clean_feed_manager

View file

@ -22,6 +22,14 @@ class SoftwareUpdate < ApplicationRecord
Gem::Version.new(version)
end
def outdated?
runtime_version >= gem_version
end
def pending?
gem_version > runtime_version
end
class << self
def check_enabled?
Rails.configuration.x.mastodon.software_update_url.present?
@ -30,7 +38,7 @@ class SoftwareUpdate < ApplicationRecord
def pending_to_a
return [] unless check_enabled?
all.to_a.filter { |update| update.gem_version > Mastodon::Version.gem_version }
all.to_a.filter(&:pending?)
end
def urgent_pending?
@ -45,4 +53,10 @@ class SoftwareUpdate < ApplicationRecord
pending_to_a.any?(&:patch_type?)
end
end
private
def runtime_version
Mastodon::Version.gem_version
end
end

View file

@ -555,19 +555,19 @@ class Status < ApplicationRecord
end
def favourites_map(status_ids, account_id)
Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
Favourite.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
end
def bookmarks_map(status_ids, account_id)
Bookmark.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h
Bookmark.select(:status_id).where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h
end
def reblogs_map(status_ids, account_id)
unscoped.select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
unscoped.select(:reblog_of_id).where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
end
def mutes_map(conversation_ids, account_id)
ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
ConversationMute.select(:conversation_id).where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
end
def blocks_map(account_ids, account_id)
@ -579,7 +579,7 @@ class Status < ApplicationRecord
end
def pins_map(status_ids, account_id)
StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
StatusPin.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
end
def emoji_reaction_allows_map(status_ids, account_id)

View file

@ -63,7 +63,7 @@ class Tag < ApplicationRecord
scope :recently_used, lambda { |account|
joins(:statuses)
.where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) })
.group(:id).order(Arel.sql('count(*) desc'))
.group(:id).order(Arel.star.count.desc)
}
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
@ -129,7 +129,7 @@ class Tag < ApplicationRecord
query = query.merge(Tag.listable) if options[:exclude_unlistable]
query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed]
query.order(Arel.sql('length(name) ASC, name ASC'))
query.order(Arel.sql('LENGTH(name)').asc, name: :asc)
.limit(limit)
.offset(offset)
end

View file

@ -303,6 +303,15 @@ class User < ApplicationRecord
save!
end
def applications_last_used
Doorkeeper::AccessToken
.where(resource_owner_id: id)
.where.not(last_used_at: nil)
.group(:application_id)
.maximum(:last_used_at)
.to_h
end
def token_for_app(app)
return nil if app.nil? || app.owner != self
@ -480,13 +489,7 @@ class User < ApplicationRecord
# Doing this conditionally is not very satisfying, but this is consistent
# with the MX records validations we do and keeps the specs tractable.
unless self.class.skip_mx_check?
Resolv::DNS.open do |dns|
dns.timeouts = 5
records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }.compact_blank
end
end
records = DomainResource.new(domain).mx unless self.class.skip_mx_check?
EmailDomainBlock.requires_approval?(records + [domain], attempt_ip: sign_up_ip)
end