.circleci
.github
app
chewy
controllers
helpers
javascript
lib
mailers
models
admin
concerns
account_associations.rb
account_avatar.rb
account_counters.rb
account_finder_concern.rb
account_header.rb
account_interactions.rb
attachmentable.rb
cacheable.rb
domain_normalizable.rb
expireable.rb
ldap_authenticable.rb
omniauthable.rb
paginable.rb
pam_authenticable.rb
rate_limitable.rb
redisable.rb
relationship_cacheable.rb
remotable.rb
status_threading_concern.rb
user_roles.rb
form
web
account.rb
account_alias.rb
account_conversation.rb
account_domain_block.rb
account_filter.rb
account_identity_proof.rb
account_migration.rb
account_moderation_note.rb
account_pin.rb
account_stat.rb
account_tag_stat.rb
account_warning.rb
account_warning_preset.rb
admin.rb
announcement.rb
announcement_filter.rb
announcement_mute.rb
announcement_reaction.rb
application_record.rb
backup.rb
block.rb
bookmark.rb
context.rb
conversation.rb
conversation_mute.rb
custom_emoji.rb
custom_emoji_category.rb
custom_emoji_filter.rb
custom_filter.rb
device.rb
domain_allow.rb
domain_block.rb
email_domain_block.rb
encrypted_message.rb
export.rb
favourite.rb
featured_tag.rb
feed.rb
follow.rb
follow_request.rb
home_feed.rb
identity.rb
import.rb
instance.rb
instance_filter.rb
invite.rb
invite_filter.rb
list.rb
list_account.rb
list_feed.rb
marker.rb
media_attachment.rb
mention.rb
message_franking.rb
mute.rb
notification.rb
one_time_key.rb
poll.rb
poll_vote.rb
preview_card.rb
relationship_filter.rb
relay.rb
remote_follow.rb
report.rb
report_filter.rb
report_note.rb
scheduled_status.rb
search.rb
session_activation.rb
setting.rb
site_upload.rb
status.rb
status_pin.rb
status_stat.rb
system_key.rb
tag.rb
tag_filter.rb
tombstone.rb
trending_tags.rb
unavailable_domain.rb
user.rb
user_invite_request.rb
web.rb
policies
presenters
serializers
services
validators
views
workers
bin
config
db
dist
lib
log
nanobox
public
spec
streaming
vendor
.buildpacks
.codeclimate.yml
.dockerignore
.editorconfig
.env.nanobox
.env.production.sample
.env.test
.env.vagrant
.eslintignore
.eslintrc.js
.foreman
.gitattributes
.gitignore
.haml-lint.yml
.nanoignore
.nvmrc
.profile
.rspec
.rubocop.yml
.ruby-version
.sass-lint.yml
.slugignore
.yarnclean
AUTHORS.md
Aptfile
CHANGELOG.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Capfile
Dockerfile
Gemfile
Gemfile.lock
LICENSE
Procfile
Procfile.dev
README.md
Rakefile
SECURITY.md
Vagrantfile
app.json
babel.config.js
boxfile.yml
config.ru
crowdin.yml
docker-compose.yml
ide-helper.js
package.json
postcss.config.js
priv-config
scalingo.json
yarn.lock
* Remove #filter_from_context? * Create scope Status.with_accounts Retrieving AR objects should be their model's scope
129 lines
3.9 KiB
Ruby
129 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module StatusThreadingConcern
|
|
extend ActiveSupport::Concern
|
|
|
|
def ancestors(limit, account = nil)
|
|
find_statuses_from_tree_path(ancestor_ids(limit), account)
|
|
end
|
|
|
|
def descendants(limit, account = nil, max_child_id = nil, since_child_id = nil, depth = nil)
|
|
find_statuses_from_tree_path(descendant_ids(limit, max_child_id, since_child_id, depth), account, promote: true)
|
|
end
|
|
|
|
def self_replies(limit)
|
|
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit)
|
|
end
|
|
|
|
private
|
|
|
|
def ancestor_ids(limit)
|
|
key = "ancestors:#{id}"
|
|
ancestors = Rails.cache.fetch(key)
|
|
|
|
if ancestors.nil? || ancestors[:limit] < limit
|
|
ids = ancestor_statuses(limit).pluck(:id).reverse!
|
|
Rails.cache.write key, limit: limit, ids: ids
|
|
ids
|
|
else
|
|
ancestors[:ids].last(limit)
|
|
end
|
|
end
|
|
|
|
def ancestor_statuses(limit)
|
|
Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id, limit: limit])
|
|
WITH RECURSIVE search_tree(id, in_reply_to_id, path)
|
|
AS (
|
|
SELECT id, in_reply_to_id, ARRAY[id]
|
|
FROM statuses
|
|
WHERE id = :id
|
|
UNION ALL
|
|
SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id
|
|
FROM search_tree
|
|
JOIN statuses ON statuses.id = search_tree.in_reply_to_id
|
|
WHERE NOT statuses.id = ANY(path)
|
|
)
|
|
SELECT id
|
|
FROM search_tree
|
|
ORDER BY path
|
|
LIMIT :limit
|
|
SQL
|
|
end
|
|
|
|
def descendant_ids(limit, max_child_id, since_child_id, depth)
|
|
descendant_statuses(limit, max_child_id, since_child_id, depth).pluck(:id)
|
|
end
|
|
|
|
def descendant_statuses(limit, max_child_id, since_child_id, depth)
|
|
# use limit + 1 and depth + 1 because 'self' is included
|
|
depth += 1 if depth.present?
|
|
limit += 1 if limit.present?
|
|
|
|
descendants_with_self = Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth])
|
|
WITH RECURSIVE search_tree(id, path)
|
|
AS (
|
|
SELECT id, ARRAY[id]
|
|
FROM statuses
|
|
WHERE id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE)
|
|
UNION ALL
|
|
SELECT statuses.id, path || statuses.id
|
|
FROM search_tree
|
|
JOIN statuses ON statuses.in_reply_to_id = search_tree.id
|
|
WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path)
|
|
)
|
|
SELECT id
|
|
FROM search_tree
|
|
ORDER BY path
|
|
LIMIT :limit
|
|
SQL
|
|
|
|
descendants_with_self - [self]
|
|
end
|
|
|
|
def find_statuses_from_tree_path(ids, account, promote: false)
|
|
statuses = Status.with_accounts(ids).to_a
|
|
account_ids = statuses.map(&:account_id).uniq
|
|
domains = statuses.map(&:account_domain).compact.uniq
|
|
relations = relations_map_for_account(account, account_ids, domains)
|
|
|
|
statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? }
|
|
|
|
# Order ancestors/descendants by tree path
|
|
statuses.sort_by! { |status| ids.index(status.id) }
|
|
|
|
# Bring self-replies to the top
|
|
if promote
|
|
promote_by!(statuses) { |status| status.in_reply_to_account_id == status.account_id }
|
|
else
|
|
statuses
|
|
end
|
|
end
|
|
|
|
def promote_by!(arr)
|
|
insert_at = arr.find_index { |item| !yield(item) }
|
|
|
|
return arr if insert_at.nil?
|
|
|
|
arr.each_with_index do |item, index|
|
|
next if index <= insert_at || !yield(item)
|
|
|
|
arr.insert(insert_at, arr.delete_at(index))
|
|
insert_at += 1
|
|
end
|
|
|
|
arr
|
|
end
|
|
|
|
def relations_map_for_account(account, account_ids, domains)
|
|
return {} if account.nil?
|
|
|
|
{
|
|
blocking: Account.blocking_map(account_ids, account.id),
|
|
blocked_by: Account.blocked_by_map(account_ids, account.id),
|
|
muting: Account.muting_map(account_ids, account.id),
|
|
following: Account.following_map(account_ids, account.id),
|
|
domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
|
|
}
|
|
end
|
|
end
|