mirror of
https://github.com/mastodon/mastodon.git
synced 2025-07-14 08:18:15 +00:00
Compare commits
2 Commits
98fb122ede
...
cd6a8305f0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd6a8305f0 | ||
![]() |
76d708dd08 |
|
@ -41,7 +41,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
|
|
||||||
ancestors_limit = CONTEXT_LIMIT
|
ancestors_limit = CONTEXT_LIMIT
|
||||||
descendants_limit = CONTEXT_LIMIT
|
descendants_limit = CONTEXT_LIMIT
|
||||||
descendants_depth_limit = nil
|
descendants_depth_limit = CONTEXT_LIMIT
|
||||||
|
|
||||||
if current_account.nil?
|
if current_account.nil?
|
||||||
ancestors_limit = ANCESTORS_LIMIT
|
ancestors_limit = ANCESTORS_LIMIT
|
||||||
|
@ -50,14 +50,14 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
|
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
|
||||||
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
|
descendants_results = @status.descendants(current_account, limit: descendants_limit, depth: descendants_depth_limit).select { |result| result.is_a?(Status) }
|
||||||
loaded_ancestors = preload_collection(ancestors_results, Status)
|
loaded_ancestors = preload_collection(ancestors_results, Status)
|
||||||
loaded_descendants = preload_collection(descendants_results, Status)
|
loaded_descendants = preload_collection(descendants_results, Status)
|
||||||
|
|
||||||
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
||||||
statuses = [@status] + @context.ancestors + @context.descendants
|
statuses = [@status] + @context.ancestors + @context.descendants
|
||||||
|
|
||||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
render json: @context, serializer: REST::V1::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||||
|
|
||||||
ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
|
ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
|
||||||
end
|
end
|
||||||
|
|
23
app/controllers/api/v2/statuses_controller.rb
Normal file
23
app/controllers/api/v2/statuses_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V2::StatusesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
|
before_action :set_status
|
||||||
|
|
||||||
|
def context
|
||||||
|
descendants_results = @status.descendants(current_account, limit: 3, depth: 2, after_id: nil)
|
||||||
|
@context = Context.new(ancestors: [], descendants: descendants_results)
|
||||||
|
render json: @context, serializer: REST::ContextSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:id])
|
||||||
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,8 +24,25 @@ module Status::ThreadingConcern
|
||||||
find_statuses_from_tree_path(ancestor_ids(limit), account)
|
find_statuses_from_tree_path(ancestor_ids(limit), account)
|
||||||
end
|
end
|
||||||
|
|
||||||
def descendants(limit, account = nil, depth = nil)
|
def descendants(account, limit:, depth:, after_id:)
|
||||||
find_statuses_from_tree_path(descendant_ids(limit, depth), account, promote: true)
|
tree = descendant_ids(limit:, depth:, after_id:)
|
||||||
|
|
||||||
|
statuses_map = Status.with_accounts(tree.reject { |id_or_placeholder| id_or_placeholder.is_a?(Context::Gap) }).index_by(&:id)
|
||||||
|
account_ids = statuses_map.values.map(&:account_id).uniq
|
||||||
|
domains = statuses_map.values.filter_map(&:account_domain).uniq
|
||||||
|
relations = account&.relations_map(account_ids, domains) || {}
|
||||||
|
|
||||||
|
statuses_map.values.each do |status|
|
||||||
|
statuses_map[status.id] = Context::FilterGap.new(id: status.id) if StatusFilter.new(status, account, relations).filtered?
|
||||||
|
end
|
||||||
|
|
||||||
|
tree.map do |id_or_placeholder|
|
||||||
|
if id_or_placeholder.is_a?(Context::Gap)
|
||||||
|
id_or_placeholder
|
||||||
|
else
|
||||||
|
statuses_map[id_or_placeholder]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_replies(limit)
|
def self_replies(limit)
|
||||||
|
@ -67,29 +84,51 @@ module Status::ThreadingConcern
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
def descendant_ids(limit, depth)
|
def descendant_ids(after_id:, limit:, depth:)
|
||||||
# use limit + 1 and depth + 1 because 'self' is included
|
# We also fetch nodes that are one level deeper than requested so we can create pagination markers
|
||||||
depth += 1 if depth.present?
|
descendant_leaves = Status.find_by_sql([<<-SQL.squish, id: id, after_id: after_id || 0, account_id: account_id, limit: limit, depth: depth])
|
||||||
limit += 1 if limit.present?
|
WITH RECURSIVE search_tree(id, account_id, path) AS (
|
||||||
|
(
|
||||||
descendants_with_self = Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, depth: depth])
|
SELECT statuses.id, statuses.account_id, ARRAY[statuses.id]
|
||||||
WITH RECURSIVE search_tree(id, path) AS (
|
|
||||||
SELECT id, ARRAY[id]
|
|
||||||
FROM statuses
|
FROM statuses
|
||||||
WHERE id = :id
|
WHERE statuses.in_reply_to_id = :id
|
||||||
|
AND statuses.id > :after_id
|
||||||
|
LIMIT :limit + 1
|
||||||
|
)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT statuses.id, path || statuses.id
|
SELECT statuses.id, statuses.account_id, path || statuses.id
|
||||||
FROM search_tree
|
FROM search_tree
|
||||||
JOIN statuses ON statuses.in_reply_to_id = search_tree.id
|
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)
|
WHERE array_length(path, 1) < :depth + 1 AND NOT statuses.id = ANY(path)
|
||||||
)
|
)
|
||||||
SELECT id
|
SELECT id, path
|
||||||
FROM search_tree
|
FROM search_tree
|
||||||
ORDER BY path
|
ORDER BY CASE WHEN account_id = :account_id THEN 1 ELSE 0 END DESC, path ASC
|
||||||
LIMIT :limit
|
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
descendants_with_self.pluck(:id) - [id]
|
current_top_level_leaf = nil
|
||||||
|
top_level_leaves = 0
|
||||||
|
past_cut_off = false
|
||||||
|
|
||||||
|
descendant_leaves.filter_map do |result|
|
||||||
|
if result.path.size == 1
|
||||||
|
if top_level_leaves == limit
|
||||||
|
past_cut_off = true
|
||||||
|
next Context::LimitGap.new(id: current_top_level_leaf.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
current_top_level_leaf = result
|
||||||
|
top_level_leaves += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if past_cut_off
|
||||||
|
nil
|
||||||
|
elsif result.path.size > depth # Nodes that are deeper than requested are pagination markers
|
||||||
|
Context::DepthGap.new(id: result.path[result.path.size - 2])
|
||||||
|
else
|
||||||
|
result.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_statuses_from_tree_path(ids, account, promote: false)
|
def find_statuses_from_tree_path(ids, account, promote: false)
|
||||||
|
|
|
@ -2,4 +2,12 @@
|
||||||
|
|
||||||
class Context < ActiveModelSerializers::Model
|
class Context < ActiveModelSerializers::Model
|
||||||
attributes :ancestors, :descendants
|
attributes :ancestors, :descendants
|
||||||
|
|
||||||
|
class Gap < ActiveModelSerializers::Model
|
||||||
|
attributes :id
|
||||||
|
end
|
||||||
|
|
||||||
|
class DepthGap < Gap; end
|
||||||
|
class LimitGap < Gap; end
|
||||||
|
class FilterGap < Gap; end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,44 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::ContextSerializer < ActiveModel::Serializer
|
class REST::ContextSerializer < ActiveModel::Serializer
|
||||||
has_many :ancestors, serializer: REST::StatusSerializer
|
class DepthGapSerializer < ActiveModel::Serializer
|
||||||
has_many :descendants, serializer: REST::StatusSerializer
|
attributes :more_under
|
||||||
|
|
||||||
|
def more_under
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LimitGapSerializer < ActiveModel::Serializer
|
||||||
|
attributes :more_after
|
||||||
|
|
||||||
|
def more_after
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class FilterGapSerializer < ActiveModel::Serializer
|
||||||
|
attributes :filtered
|
||||||
|
|
||||||
|
def filtered
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.serializer_for(model, options)
|
||||||
|
case model.class.name
|
||||||
|
when 'Status'
|
||||||
|
REST::StatusSerializer
|
||||||
|
when 'Context::DepthGap'
|
||||||
|
DepthGapSerializer
|
||||||
|
when 'Context::LimitGap'
|
||||||
|
LimitGapSerializer
|
||||||
|
when 'Context::FilterGap'
|
||||||
|
FilterGapSerializer
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
has_many :descendants
|
||||||
end
|
end
|
||||||
|
|
6
app/serializers/rest/v1/context_serializer.rb
Normal file
6
app/serializers/rest/v1/context_serializer.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::V1::ContextSerializer < ActiveModel::Serializer
|
||||||
|
has_many :ancestors, serializer: REST::StatusSerializer
|
||||||
|
has_many :descendants, serializer: REST::StatusSerializer
|
||||||
|
end
|
|
@ -356,6 +356,12 @@ namespace :api, format: false do
|
||||||
|
|
||||||
resources :accounts, only: [:index], module: :notifications
|
resources :accounts, only: [:index], module: :notifications
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :statuses, only: [] do
|
||||||
|
member do
|
||||||
|
get :context
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :web do
|
namespace :web do
|
||||||
|
|
Loading…
Reference in New Issue
Block a user