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
|
||||
descendants_limit = CONTEXT_LIMIT
|
||||
descendants_depth_limit = nil
|
||||
descendants_depth_limit = CONTEXT_LIMIT
|
||||
|
||||
if current_account.nil?
|
||||
ancestors_limit = ANCESTORS_LIMIT
|
||||
|
@ -50,14 +50,14 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
end
|
||||
|
||||
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_descendants = preload_collection(descendants_results, Status)
|
||||
|
||||
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_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?
|
||||
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)
|
||||
end
|
||||
|
||||
def descendants(limit, account = nil, depth = nil)
|
||||
find_statuses_from_tree_path(descendant_ids(limit, depth), account, promote: true)
|
||||
def descendants(account, limit:, depth:, after_id:)
|
||||
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
|
||||
|
||||
def self_replies(limit)
|
||||
|
@ -67,29 +84,51 @@ module Status::ThreadingConcern
|
|||
SQL
|
||||
end
|
||||
|
||||
def descendant_ids(limit, 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, depth: depth])
|
||||
WITH RECURSIVE search_tree(id, path) AS (
|
||||
SELECT id, ARRAY[id]
|
||||
FROM statuses
|
||||
WHERE id = :id
|
||||
def descendant_ids(after_id:, limit:, depth:)
|
||||
# We also fetch nodes that are one level deeper than requested so we can create pagination markers
|
||||
descendant_leaves = Status.find_by_sql([<<-SQL.squish, id: id, after_id: after_id || 0, account_id: account_id, limit: limit, depth: depth])
|
||||
WITH RECURSIVE search_tree(id, account_id, path) AS (
|
||||
(
|
||||
SELECT statuses.id, statuses.account_id, ARRAY[statuses.id]
|
||||
FROM statuses
|
||||
WHERE statuses.in_reply_to_id = :id
|
||||
AND statuses.id > :after_id
|
||||
LIMIT :limit + 1
|
||||
)
|
||||
UNION ALL
|
||||
SELECT statuses.id, path || statuses.id
|
||||
SELECT statuses.id, statuses.account_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)
|
||||
WHERE array_length(path, 1) < :depth + 1 AND NOT statuses.id = ANY(path)
|
||||
)
|
||||
SELECT id
|
||||
SELECT id, path
|
||||
FROM search_tree
|
||||
ORDER BY path
|
||||
LIMIT :limit
|
||||
ORDER BY CASE WHEN account_id = :account_id THEN 1 ELSE 0 END DESC, path ASC
|
||||
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
|
||||
|
||||
def find_statuses_from_tree_path(ids, account, promote: false)
|
||||
|
|
|
@ -2,4 +2,12 @@
|
|||
|
||||
class Context < ActiveModelSerializers::Model
|
||||
attributes :ancestors, :descendants
|
||||
|
||||
class Gap < ActiveModelSerializers::Model
|
||||
attributes :id
|
||||
end
|
||||
|
||||
class DepthGap < Gap; end
|
||||
class LimitGap < Gap; end
|
||||
class FilterGap < Gap; end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ContextSerializer < ActiveModel::Serializer
|
||||
has_many :ancestors, serializer: REST::StatusSerializer
|
||||
has_many :descendants, serializer: REST::StatusSerializer
|
||||
class DepthGapSerializer < ActiveModel::Serializer
|
||||
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
|
||||
|
|
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
|
||||
end
|
||||
|
||||
resources :statuses, only: [] do
|
||||
member do
|
||||
get :context
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :web do
|
||||
|
|
Loading…
Reference in New Issue
Block a user