Merge branch 'main' into glitch-soc/merge-upstream
- `app/views/statuses/_simple_status.html.haml`: Small markup change in glitch-soc, on a line that has been modified by upstream. Ported upstream changes.
This commit is contained in:
@@ -115,7 +115,6 @@ class Account < ApplicationRecord
|
||||
scope :searchable, -> { 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 :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
|
||||
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_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 :popular, -> { order('account_stats.followers_count desc') }
|
||||
@@ -283,19 +282,13 @@ class Account < ApplicationRecord
|
||||
if hashtags_map.key?(tag.name)
|
||||
hashtags_map.delete(tag.name)
|
||||
else
|
||||
transaction do
|
||||
tags.delete(tag)
|
||||
tag.decrement_count!(:accounts_count)
|
||||
end
|
||||
tags.delete(tag)
|
||||
end
|
||||
end
|
||||
|
||||
# Add hashtags that were so far missing
|
||||
hashtags_map.each_value do |tag|
|
||||
transaction do
|
||||
tags << tag
|
||||
tag.increment_count!(:accounts_count)
|
||||
end
|
||||
tags << tag
|
||||
end
|
||||
end
|
||||
|
||||
|
@@ -1,17 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions
|
||||
class Suggestion < ActiveModelSerializers::Model
|
||||
attributes :account, :source
|
||||
end
|
||||
SOURCES = [
|
||||
AccountSuggestions::SettingSource,
|
||||
AccountSuggestions::PastInteractionsSource,
|
||||
AccountSuggestions::GlobalSource,
|
||||
].freeze
|
||||
|
||||
def self.get(account, limit)
|
||||
suggestions = PotentialFriendshipTracker.get(account, limit).map { |target_account| Suggestion.new(account: target_account, source: :past_interaction) }
|
||||
suggestions.concat(FollowRecommendation.get(account, limit - suggestions.size, suggestions.map { |suggestion| suggestion.account.id }).map { |target_account| Suggestion.new(account: target_account, source: :global) }) if suggestions.size < limit
|
||||
suggestions
|
||||
SOURCES.each_with_object([]) do |source_class, suggestions|
|
||||
source_suggestions = source_class.new.get(
|
||||
account,
|
||||
skip_account_ids: suggestions.map(&:account_id),
|
||||
limit: limit - suggestions.size
|
||||
)
|
||||
|
||||
suggestions.concat(source_suggestions)
|
||||
end
|
||||
end
|
||||
|
||||
def self.remove(account, target_account_id)
|
||||
PotentialFriendshipTracker.remove(account.id, target_account_id)
|
||||
SOURCES.each do |source_class|
|
||||
source = source_class.new
|
||||
source.remove(account, target_account_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
37
app/models/account_suggestions/global_source.rb
Normal file
37
app/models/account_suggestions/global_source.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::GlobalSource < AccountSuggestions::Source
|
||||
def key
|
||||
:global
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
account_ids = account_ids_for_locale(account.user_locale) - [account.id] - skip_account_ids
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope(account).where(id: account_ids),
|
||||
account_ids
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(_account, _target_account_id)
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope(account)
|
||||
Account.searchable
|
||||
.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
end
|
||||
|
||||
def account_ids_for_locale(locale)
|
||||
Redis.current.zrevrange("follow_recommendations:#{locale}", 0, -1).map(&:to_i)
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
account.id
|
||||
end
|
||||
end
|
36
app/models/account_suggestions/past_interactions_source.rb
Normal file
36
app/models/account_suggestions/past_interactions_source.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::PastInteractionsSource < AccountSuggestions::Source
|
||||
include Redisable
|
||||
|
||||
def key
|
||||
:past_interactions
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
account_ids = account_ids_for_account(account.id, limit + skip_account_ids.size) - skip_account_ids
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope.where(id: account_ids),
|
||||
account_ids
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(account, target_account_id)
|
||||
redis.zrem("interactions:#{account.id}", target_account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
Account.searchable
|
||||
end
|
||||
|
||||
def account_ids_for_account(account_id, limit)
|
||||
redis.zrevrange("interactions:#{account_id}", 0, limit).map(&:to_i)
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
account.id
|
||||
end
|
||||
end
|
68
app/models/account_suggestions/setting_source.rb
Normal file
68
app/models/account_suggestions/setting_source.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::SettingSource < AccountSuggestions::Source
|
||||
def key
|
||||
:staff
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
return [] unless setting_enabled?
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope(account).where(setting_to_where_condition).where.not(id: skip_account_ids),
|
||||
usernames_and_domains
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(_account, _target_account_id)
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope(account)
|
||||
Account.searchable
|
||||
.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
.where(locked: false)
|
||||
.where.not(id: account.id)
|
||||
end
|
||||
|
||||
def usernames_and_domains
|
||||
@usernames_and_domains ||= setting_to_usernames_and_domains
|
||||
end
|
||||
|
||||
def setting_enabled?
|
||||
setting.present?
|
||||
end
|
||||
|
||||
def setting_to_where_condition
|
||||
usernames_and_domains.map do |(username, domain)|
|
||||
Arel::Nodes::Grouping.new(
|
||||
Account.arel_table[:username].lower.eq(username.downcase).and(
|
||||
Account.arel_table[:domain].lower.eq(domain&.downcase)
|
||||
)
|
||||
)
|
||||
end.reduce(:or)
|
||||
end
|
||||
|
||||
def setting_to_usernames_and_domains
|
||||
setting.split(',').map do |str|
|
||||
username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
|
||||
domain = nil if TagManager.instance.local_domain?(domain)
|
||||
|
||||
next if username.blank?
|
||||
|
||||
[username, domain]
|
||||
end.compact
|
||||
end
|
||||
|
||||
def setting
|
||||
Setting.bootstrap_timeline_accounts
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
[account.username, account.domain]
|
||||
end
|
||||
end
|
34
app/models/account_suggestions/source.rb
Normal file
34
app/models/account_suggestions/source.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::Source
|
||||
def key
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def get(_account, **kwargs)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def remove(_account, target_account_id)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def as_ordered_suggestions(scope, ordered_list)
|
||||
return [] if ordered_list.empty?
|
||||
|
||||
map = scope.index_by(&method(:to_ordered_list_key))
|
||||
|
||||
ordered_list.map { |ordered_list_key| map[ordered_list_key] }.compact.map do |account|
|
||||
AccountSuggestions::Suggestion.new(
|
||||
account: account,
|
||||
source: key
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def to_ordered_list_key(_account)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
7
app/models/account_suggestions/suggestion.rb
Normal file
7
app/models/account_suggestions/suggestion.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::Suggestion < ActiveModelSerializers::Model
|
||||
attributes :account, :source
|
||||
|
||||
delegate :id, to: :account, prefix: true
|
||||
end
|
@@ -1,24 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: account_tag_stats
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# tag_id :bigint(8) not null
|
||||
# accounts_count :bigint(8) default(0), not null
|
||||
# hidden :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class AccountTagStat < ApplicationRecord
|
||||
belongs_to :tag, inverse_of: :account_tag_stat
|
||||
|
||||
def increment_count!(key)
|
||||
update(key => public_send(key) + 1)
|
||||
end
|
||||
|
||||
def decrement_count!(key)
|
||||
update(key => [public_send(key) - 1, 0].max)
|
||||
end
|
||||
end
|
@@ -17,12 +17,14 @@ class Admin::ActionLogFilter
|
||||
create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze,
|
||||
create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
|
||||
create_email_domain_block: { target_type: 'EmailDomainBlock', action: 'create' }.freeze,
|
||||
create_unavailable_domain: { target_type: 'UnavailableDomain', action: 'create' }.freeze,
|
||||
demote_user: { target_type: 'User', action: 'demote' }.freeze,
|
||||
destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze,
|
||||
destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze,
|
||||
destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
|
||||
destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
|
||||
destroy_email_domain_block: { target_type: 'EmailDomainBlock', action: 'destroy' }.freeze,
|
||||
destroy_unavailable_domain: { target_type: 'UnavailableDomain', action: 'destroy' }.freeze,
|
||||
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
|
||||
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
|
||||
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
|
||||
|
@@ -184,6 +184,14 @@ module AccountInteractions
|
||||
active_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
|
||||
def following_anyone?
|
||||
active_relationships.exists?
|
||||
end
|
||||
|
||||
def not_following_anyone?
|
||||
!following_anyone?
|
||||
end
|
||||
|
||||
def blocking?(other_account)
|
||||
block_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
|
@@ -14,26 +14,13 @@ class FollowRecommendation < ApplicationRecord
|
||||
belongs_to :account_summary, foreign_key: :account_id
|
||||
belongs_to :account, foreign_key: :account_id
|
||||
|
||||
scope :safe, -> { joins(:account_summary).merge(AccountSummary.safe) }
|
||||
scope :localized, ->(locale) { joins(:account_summary).merge(AccountSummary.localized(locale)) }
|
||||
scope :filtered, -> { joins(:account_summary).merge(AccountSummary.filtered) }
|
||||
|
||||
def self.refresh
|
||||
Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false)
|
||||
end
|
||||
|
||||
def readonly?
|
||||
true
|
||||
end
|
||||
|
||||
def self.get(account, limit, exclude_account_ids = [])
|
||||
account_ids = Redis.current.zrevrange("follow_recommendations:#{account.user_locale}", 0, -1).map(&:to_i) - exclude_account_ids - [account.id]
|
||||
|
||||
return [] if account_ids.empty? || limit < 1
|
||||
|
||||
accounts = Account.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
.where(id: account_ids)
|
||||
.limit(limit)
|
||||
.index_by(&:id)
|
||||
|
||||
account_ids.map { |id| accounts[id] }.compact
|
||||
end
|
||||
end
|
||||
|
@@ -29,7 +29,7 @@ class FollowRequest < ApplicationRecord
|
||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||
|
||||
def authorize!
|
||||
account.follow!(target_account, reblogs: show_reblogs, notify: notify, uri: uri)
|
||||
account.follow!(target_account, reblogs: show_reblogs, notify: notify, uri: uri, bypass_limit: true)
|
||||
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
||||
destroy!
|
||||
end
|
||||
|
@@ -16,7 +16,6 @@ class Form::AdminSettings
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
enable_bootstrap_timeline_accounts
|
||||
bootstrap_timeline_accounts
|
||||
flavour
|
||||
skin
|
||||
@@ -48,7 +47,6 @@ class Form::AdminSettings
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
enable_bootstrap_timeline_accounts
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
|
@@ -10,10 +10,13 @@
|
||||
class Instance < ApplicationRecord
|
||||
self.primary_key = :domain
|
||||
|
||||
attr_accessor :failure_days
|
||||
|
||||
has_many :accounts, foreign_key: :domain, primary_key: :domain
|
||||
|
||||
belongs_to :domain_block, foreign_key: :domain, primary_key: :domain
|
||||
belongs_to :domain_allow, foreign_key: :domain, primary_key: :domain
|
||||
belongs_to :unavailable_domain, foreign_key: :domain, primary_key: :domain # skipcq: RB-RL1031
|
||||
|
||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||
|
||||
|
@@ -4,6 +4,8 @@ class InstanceFilter
|
||||
KEYS = %i(
|
||||
limited
|
||||
by_domain
|
||||
warning
|
||||
unavailable
|
||||
).freeze
|
||||
|
||||
attr_reader :params
|
||||
@@ -13,7 +15,7 @@ class InstanceFilter
|
||||
end
|
||||
|
||||
def results
|
||||
scope = Instance.includes(:domain_block, :domain_allow).order(accounts_count: :desc)
|
||||
scope = Instance.includes(:domain_block, :domain_allow, :unavailable_domain).order(accounts_count: :desc)
|
||||
|
||||
params.each do |key, value|
|
||||
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
|
||||
@@ -32,6 +34,10 @@ class InstanceFilter
|
||||
Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
|
||||
when 'by_domain'
|
||||
Instance.matches_domain(value)
|
||||
when 'warning'
|
||||
Instance.where(domain: DeliveryFailureTracker.warning_domains)
|
||||
when 'unavailable'
|
||||
Instance.joins(:unavailable_domain)
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
|
@@ -287,7 +287,7 @@ class MediaAttachment < ApplicationRecord
|
||||
if instance.file_content_type == 'image/gif'
|
||||
[:gif_transcoder, :blurhash_transcoder]
|
||||
elsif VIDEO_MIME_TYPES.include?(instance.file_content_type)
|
||||
[:video_transcoder, :blurhash_transcoder, :type_corrector]
|
||||
[:transcoder, :blurhash_transcoder, :type_corrector]
|
||||
elsif AUDIO_MIME_TYPES.include?(instance.file_content_type)
|
||||
[:image_extractor, :transcoder, :type_corrector]
|
||||
else
|
||||
@@ -388,7 +388,7 @@ class MediaAttachment < ApplicationRecord
|
||||
# paths but ultimately the same file, so it makes sense to memoize the
|
||||
# result while disregarding the path
|
||||
def ffmpeg_data(path = nil)
|
||||
@ffmpeg_data ||= FFMPEG::Movie.new(path)
|
||||
@ffmpeg_data ||= VideoMetadataExtractor.new(path)
|
||||
end
|
||||
|
||||
def enqueue_processing
|
||||
|
@@ -44,7 +44,7 @@ class SessionActivation < ApplicationRecord
|
||||
end
|
||||
|
||||
def activate(**options)
|
||||
activation = create!(options)
|
||||
activation = create!(**options)
|
||||
purge_old
|
||||
activation
|
||||
end
|
||||
|
@@ -167,6 +167,10 @@ class Status < ApplicationRecord
|
||||
attributes['local'] || uri.nil?
|
||||
end
|
||||
|
||||
def in_reply_to_local_account?
|
||||
reply? && thread&.account&.local?
|
||||
end
|
||||
|
||||
def reblog?
|
||||
!reblog_of_id.nil?
|
||||
end
|
||||
|
@@ -20,10 +20,8 @@
|
||||
class Tag < ApplicationRecord
|
||||
has_and_belongs_to_many :statuses
|
||||
has_and_belongs_to_many :accounts
|
||||
has_and_belongs_to_many :sample_accounts, -> { local.discoverable.popular.limit(3) }, class_name: 'Account'
|
||||
|
||||
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
||||
has_one :account_tag_stat, dependent: :destroy
|
||||
|
||||
HASHTAG_SEPARATORS = "_\u00B7\u200c"
|
||||
HASHTAG_NAME_RE = "([[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)"
|
||||
@@ -38,28 +36,11 @@ class Tag < ApplicationRecord
|
||||
scope :usable, -> { where(usable: [true, nil]) }
|
||||
scope :listable, -> { where(listable: [true, nil]) }
|
||||
scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) }
|
||||
scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
|
||||
scope :recently_used, ->(account) { joins(:statuses).where(statuses: { id: account.statuses.select(:id).limit(1000) }).group(:id).order(Arel.sql('count(*) desc')) }
|
||||
scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
|
||||
|
||||
delegate :accounts_count,
|
||||
:accounts_count=,
|
||||
:increment_count!,
|
||||
:decrement_count!,
|
||||
to: :account_tag_stat
|
||||
|
||||
after_save :save_account_tag_stat
|
||||
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
|
||||
|
||||
update_index('tags#tag', :self)
|
||||
|
||||
def account_tag_stat
|
||||
super || build_account_tag_stat
|
||||
end
|
||||
|
||||
def cached_sample_accounts
|
||||
Rails.cache.fetch("#{cache_key}/sample_accounts", expires_in: 12.hours) { sample_accounts }
|
||||
end
|
||||
|
||||
def to_param
|
||||
name
|
||||
end
|
||||
@@ -94,6 +75,10 @@ class Tag < ApplicationRecord
|
||||
requested_review_at.present?
|
||||
end
|
||||
|
||||
def use!(account, status: nil, at_time: Time.now.utc)
|
||||
TrendingTags.record_use!(self, account, status: status, at_time: at_time)
|
||||
end
|
||||
|
||||
def trending?
|
||||
TrendingTags.trending?(self)
|
||||
end
|
||||
@@ -126,10 +111,10 @@ class Tag < ApplicationRecord
|
||||
end
|
||||
|
||||
def search_for(term, limit = 5, offset = 0, options = {})
|
||||
normalized_term = normalize(term.strip)
|
||||
pattern = sanitize_sql_like(normalized_term) + '%'
|
||||
query = Tag.listable.where(arel_table[:name].lower.matches(pattern))
|
||||
query = query.where(arel_table[:name].lower.eq(normalized_term).or(arel_table[:reviewed_at].not_eq(nil))) if options[:exclude_unreviewed]
|
||||
stripped_term = term.strip
|
||||
|
||||
query = Tag.listable.matches_name(stripped_term)
|
||||
query = query.merge(matching_name(stripped_term).or(where.not(reviewed_at: nil))) if options[:exclude_unreviewed]
|
||||
|
||||
query.order(Arel.sql('length(name) ASC, name ASC'))
|
||||
.limit(limit)
|
||||
@@ -145,7 +130,7 @@ class Tag < ApplicationRecord
|
||||
end
|
||||
|
||||
def matching_name(name_or_names)
|
||||
names = Array(name_or_names).map { |name| normalize(name).mb_chars.downcase.to_s }
|
||||
names = Array(name_or_names).map { |name| arel_table.lower(normalize(name)) }
|
||||
|
||||
if names.size == 1
|
||||
where(arel_table[:name].lower.eq(names.first))
|
||||
@@ -154,8 +139,6 @@ class Tag < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize(str)
|
||||
str.gsub(/\A#/, '')
|
||||
end
|
||||
@@ -163,11 +146,6 @@ class Tag < ApplicationRecord
|
||||
|
||||
private
|
||||
|
||||
def save_account_tag_stat
|
||||
return unless account_tag_stat&.changed?
|
||||
account_tag_stat.save
|
||||
end
|
||||
|
||||
def validate_name_change
|
||||
errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.mb_chars.casecmp(name.mb_chars).zero?
|
||||
end
|
||||
|
@@ -33,8 +33,6 @@ class TagFilter
|
||||
|
||||
def scope_for(key, value)
|
||||
case key.to_s
|
||||
when 'directory'
|
||||
Tag.discoverable
|
||||
when 'reviewed'
|
||||
Tag.reviewed.order(reviewed_at: :desc)
|
||||
when 'unreviewed'
|
||||
|
@@ -13,19 +13,23 @@ class TrendingTags
|
||||
class << self
|
||||
include Redisable
|
||||
|
||||
def record_use!(tag, account, at_time = Time.now.utc)
|
||||
return if account.silenced? || account.bot? || !tag.usable? || !(tag.trendable? || tag.requires_review?)
|
||||
def record_use!(tag, account, status: nil, at_time: Time.now.utc)
|
||||
return unless tag.usable? && !account.silenced?
|
||||
|
||||
# Even if a tag is not allowed to trend, we still need to
|
||||
# record the stats since they can be displayed in other places
|
||||
increment_historical_use!(tag.id, at_time)
|
||||
increment_unique_use!(tag.id, account.id, at_time)
|
||||
increment_use!(tag.id, at_time)
|
||||
|
||||
tag.update(last_status_at: Time.now.utc) if tag.last_status_at.nil? || tag.last_status_at < 12.hours.ago
|
||||
# Only update when the tag was last used once every 12 hours
|
||||
# and only if a status is given (lets use ignore reblogs)
|
||||
tag.update(last_status_at: at_time) if status.present? && (tag.last_status_at.nil? || (tag.last_status_at < at_time && tag.last_status_at < 12.hours.ago))
|
||||
end
|
||||
|
||||
def update!(at_time = Time.now.utc)
|
||||
tag_ids = redis.smembers("#{KEY}:used:#{at_time.beginning_of_day.to_i}") + redis.zrange(KEY, 0, -1)
|
||||
tags = Tag.where(id: tag_ids.uniq)
|
||||
tags = Tag.trendable.where(id: tag_ids.uniq)
|
||||
|
||||
# First pass to calculate scores and update the set
|
||||
|
||||
|
@@ -370,15 +370,20 @@ class User < ApplicationRecord
|
||||
|
||||
protected
|
||||
|
||||
def send_devise_notification(notification, *args)
|
||||
def send_devise_notification(notification, *args, **kwargs)
|
||||
# This method can be called in `after_update` and `after_commit` hooks,
|
||||
# but we must make sure the mailer is actually called *after* commit,
|
||||
# otherwise it may work on stale data. To do this, figure out if we are
|
||||
# within a transaction.
|
||||
|
||||
# It seems like devise sends keyword arguments as a hash in the last
|
||||
# positional argument
|
||||
kwargs = args.pop if args.last.is_a?(Hash) && kwargs.empty?
|
||||
|
||||
if ActiveRecord::Base.connection.current_transaction.try(:records)&.include?(self)
|
||||
pending_devise_notifications << [notification, args]
|
||||
pending_devise_notifications << [notification, args, kwargs]
|
||||
else
|
||||
render_and_send_devise_message(notification, *args)
|
||||
render_and_send_devise_message(notification, *args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,8 +394,8 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def send_pending_devise_notifications
|
||||
pending_devise_notifications.each do |notification, args|
|
||||
render_and_send_devise_message(notification, *args)
|
||||
pending_devise_notifications.each do |notification, args, kwargs|
|
||||
render_and_send_devise_message(notification, *args, **kwargs)
|
||||
end
|
||||
|
||||
# Empty the pending notifications array because the
|
||||
@@ -403,8 +408,8 @@ class User < ApplicationRecord
|
||||
@pending_devise_notifications ||= []
|
||||
end
|
||||
|
||||
def render_and_send_devise_message(notification, *args)
|
||||
devise_mailer.send(notification, self, *args).deliver_later
|
||||
def render_and_send_devise_message(notification, *args, **kwargs)
|
||||
devise_mailer.send(notification, self, *args, **kwargs).deliver_later
|
||||
end
|
||||
|
||||
def set_approved
|
||||
@@ -458,9 +463,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def regenerate_feed!
|
||||
return unless Redis.current.setnx("account:#{account_id}:regeneration", true)
|
||||
Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds)
|
||||
RegenerationWorker.perform_async(account_id)
|
||||
RegenerationWorker.perform_async(account_id) if Redis.current.set("account:#{account_id}:regeneration", true, nx: true, ex: 1.day.seconds)
|
||||
end
|
||||
|
||||
def needs_feed_update?
|
||||
|
Reference in New Issue
Block a user