Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/api/v1/timelines/public_controller.rb
- app/lib/feed_manager.rb
- app/models/status.rb
- app/services/precompute_feed_service.rb
- app/workers/feed_insert_worker.rb
- spec/models/status_spec.rb

All conflicts are due to upstream refactoring feed management and us having
local-only toots on top of that. Rewrote local-only toots management for
upstream's changes.
This commit is contained in:
Thibaut Girka
2020-09-08 16:01:55 +02:00
28 changed files with 912 additions and 714 deletions

104
app/models/public_feed.rb Normal file
View File

@ -0,0 +1,104 @@
# frozen_string_literal: true
class PublicFeed < Feed
# @param [Account] account
# @param [Hash] options
# @option [Boolean] :with_replies
# @option [Boolean] :with_reblogs
# @option [Boolean] :local
# @option [Boolean] :remote
# @option [Boolean] :only_media
# @option [Boolean] :allow_local_only
def initialize(account, options = {})
@account = account
@options = options
end
# @param [Integer] limit
# @param [Integer] max_id
# @param [Integer] since_id
# @param [Integer] min_id
# @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil)
scope = public_scope
scope.merge!(without_local_only_scope) unless allow_local_only?
scope.merge!(without_replies_scope) unless with_replies?
scope.merge!(without_reblogs_scope) unless with_reblogs?
scope.merge!(local_only_scope) if local_only?
scope.merge!(remote_only_scope) if remote_only?
scope.merge!(account_filters_scope) if account?
scope.merge!(media_only_scope) if media_only?
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end
private
def allow_local_only?
local_account? && (local_only? || @options[:allow_local_only])
end
def with_reblogs?
@options[:with_reblogs]
end
def with_replies?
@options[:with_replies]
end
def local_only?
@options[:local]
end
def remote_only?
@options[:remote]
end
def account?
@account.present?
end
def local_account?
@account&.local?
end
def media_only?
@options[:only_media]
end
def public_scope
Status.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
end
def local_only_scope
Status.local
end
def remote_only_scope
Status.remote
end
def without_replies_scope
Status.without_replies
end
def without_reblogs_scope
Status.without_reblogs
end
def media_only_scope
Status.joins(:media_attachments).group(:id)
end
def without_local_only_scope
Status.not_local_only
end
def account_filters_scope
Status.not_excluded_by_account(@account).tap do |scope|
scope.merge!(Status.not_domain_blocked_by_account(@account)) unless local_only?
scope.merge!(Status.in_chosen_languages(@account)) if @account.chosen_languages.present?
end
end
end

View File

@ -89,12 +89,12 @@ class Status < ApplicationRecord
scope :recent, -> { reorder(id: :desc) }
scope :remote, -> { where(local: false).where.not(uri: nil) }
scope :local, -> { where(local: true).or(where(uri: nil)) }
scope :with_accounts, ->(ids) { where(id: ids).includes(:account) }
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
scope :with_public_visibility, -> { where(visibility: :public) }
scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
scope :in_chosen_languages, ->(account) { where(language: nil).or where(language: account.chosen_languages) }
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
@ -330,23 +330,6 @@ class Status < ApplicationRecord
end
end
def as_public_timeline(account = nil, local_only = false)
query = timeline_scope(local_only)
query = query.without_replies unless Setting.show_replies_in_public_timelines
apply_timeline_filters(query, account, [:local, true].include?(local_only))
end
def as_tag_timeline(tag, account = nil, local_only = false)
query = timeline_scope(local_only).tagged_with(tag)
apply_timeline_filters(query, account, local_only)
end
def as_outbox_timeline(account)
where(account: account, visibility: :public)
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 }
end
@ -423,53 +406,6 @@ class Status < ApplicationRecord
status&.distributable? ? status : nil
end.compact
end
private
def timeline_scope(scope = false)
starting_scope = case scope
when :local, true
Status.local
when :remote
Status.remote
else
Status
end
starting_scope = starting_scope.with_public_visibility
if Setting.show_reblogs_in_public_timelines
starting_scope
else
starting_scope.without_reblogs
end
end
def apply_timeline_filters(query, account, local_only)
if account.nil?
filter_timeline_default(query)
else
filter_timeline_for_account(query, account, local_only)
end
end
def filter_timeline_for_account(query, account, local_only)
query = query.not_excluded_by_account(account)
query = query.not_domain_blocked_by_account(account) unless local_only
query = query.in_chosen_languages(account) if account.chosen_languages.present?
query.merge(account_silencing_filter(account))
end
def filter_timeline_default(query)
query.not_local_only.excluding_silenced_accounts
end
def account_silencing_filter(account)
if account.silenced?
including_myself = left_outer_joins(:account).where(account_id: account.id).references(:accounts)
excluding_silenced_accounts.or(including_myself)
else
excluding_silenced_accounts
end
end
end
def marked_local_only?

View File

@ -39,7 +39,7 @@ class Tag < ApplicationRecord
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 :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('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,

58
app/models/tag_feed.rb Normal file
View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
class TagFeed < PublicFeed
LIMIT_PER_MODE = 4
# @param [Tag] tag
# @param [Account] account
# @param [Hash] options
# @option [Enumerable<String>] :any
# @option [Enumerable<String>] :all
# @option [Enumerable<String>] :none
# @option [Boolean] :local
# @option [Boolean] :remote
# @option [Boolean] :only_media
def initialize(tag, account, options = {})
@tag = tag
@account = account
@options = options
end
# @param [Integer] limit
# @param [Integer] max_id
# @param [Integer] since_id
# @param [Integer] min_id
# @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil)
scope = public_scope
scope.merge!(without_local_only_scope) unless local_account?
scope.merge!(tagged_with_any_scope)
scope.merge!(tagged_with_all_scope)
scope.merge!(tagged_with_none_scope)
scope.merge!(local_only_scope) if local_only?
scope.merge!(remote_only_scope) if remote_only?
scope.merge!(account_filters_scope) if account?
scope.merge!(media_only_scope) if media_only?
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end
private
def tagged_with_any_scope
Status.group(:id).tagged_with(tags_for(Array(@tag.name) | Array(@options[:any])))
end
def tagged_with_all_scope
Status.group(:id).tagged_with_all(tags_for(@options[:all]))
end
def tagged_with_none_scope
Status.group(:id).tagged_with_none(tags_for(@options[:none]))
end
def tags_for(names)
Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
end
end