Merge upstream (#81)
This commit is contained in:
		@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
class AccountsController < ApplicationController
 | 
			
		||||
  include AccountControllerConcern
 | 
			
		||||
  include SignatureVerification
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
@@ -15,7 +16,9 @@ class AccountsController < ApplicationController
 | 
			
		||||
        render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      format.activitystreams2
 | 
			
		||||
      format.json do
 | 
			
		||||
        render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								app/controllers/activitypub/outboxes_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/controllers/activitypub/outboxes_controller.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class ActivityPub::OutboxesController < Api::BaseController
 | 
			
		||||
  before_action :set_account
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
 | 
			
		||||
    @statuses = cache_collection(@statuses, Status)
 | 
			
		||||
 | 
			
		||||
    render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def set_account
 | 
			
		||||
    @account = Account.find_local!(params[:account_username])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def outbox_presenter
 | 
			
		||||
    ActivityPub::CollectionPresenter.new(
 | 
			
		||||
      id: account_outbox_url(@account),
 | 
			
		||||
      type: :ordered,
 | 
			
		||||
      current: account_outbox_url(@account),
 | 
			
		||||
      size: @account.statuses_count,
 | 
			
		||||
      items: @statuses
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Api::ActivityPub::ActivitiesController < Api::BaseController
 | 
			
		||||
  include Authorization
 | 
			
		||||
 | 
			
		||||
  # before_action :set_follow, only: [:show_follow]
 | 
			
		||||
  before_action :set_status, only: [:show_status]
 | 
			
		||||
 | 
			
		||||
  respond_to :activitystreams2
 | 
			
		||||
 | 
			
		||||
  # Show a status in AS2 format, as either an Announce (reblog) or a Create (post) activity.
 | 
			
		||||
  def show_status
 | 
			
		||||
    authorize @status, :show?
 | 
			
		||||
 | 
			
		||||
    if @status.reblog?
 | 
			
		||||
      render :show_status_announce
 | 
			
		||||
    else
 | 
			
		||||
      render :show_status_create
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def set_status
 | 
			
		||||
    @status = Status.find(params[:id])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Api::ActivityPub::NotesController < Api::BaseController
 | 
			
		||||
  include Authorization
 | 
			
		||||
 | 
			
		||||
  before_action :set_status
 | 
			
		||||
 | 
			
		||||
  respond_to :activitystreams2
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    authorize @status, :show?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def set_status
 | 
			
		||||
    @status = Status.find(params[:id])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,69 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Api::ActivityPub::OutboxController < Api::BaseController
 | 
			
		||||
  before_action :set_account
 | 
			
		||||
 | 
			
		||||
  respond_to :activitystreams2
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    if params[:max_id] || params[:since_id]
 | 
			
		||||
      show_outbox_page
 | 
			
		||||
    else
 | 
			
		||||
      show_base_outbox
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def show_base_outbox
 | 
			
		||||
    @statuses = Status.as_outbox_timeline(@account)
 | 
			
		||||
    @statuses = cache_collection(@statuses)
 | 
			
		||||
 | 
			
		||||
    set_maps(@statuses)
 | 
			
		||||
 | 
			
		||||
    set_first_last_page(@statuses)
 | 
			
		||||
 | 
			
		||||
    render :show
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def show_outbox_page
 | 
			
		||||
    all_statuses = Status.as_outbox_timeline(@account)
 | 
			
		||||
    @statuses = all_statuses.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
 | 
			
		||||
 | 
			
		||||
    all_statuses = cache_collection(all_statuses)
 | 
			
		||||
    @statuses = cache_collection(@statuses)
 | 
			
		||||
 | 
			
		||||
    set_maps(@statuses)
 | 
			
		||||
 | 
			
		||||
    set_first_last_page(all_statuses)
 | 
			
		||||
 | 
			
		||||
    @next_page_url = api_activitypub_outbox_url(pagination_params(max_id: @statuses.last.id))    unless @statuses.empty?
 | 
			
		||||
    @prev_page_url = api_activitypub_outbox_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
 | 
			
		||||
 | 
			
		||||
    @paginated = @next_page_url || @prev_page_url
 | 
			
		||||
    @part_of_url = api_activitypub_outbox_url
 | 
			
		||||
 | 
			
		||||
    set_pagination_headers(@next_page_url, @prev_page_url)
 | 
			
		||||
 | 
			
		||||
    render :show_page
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def cache_collection(raw)
 | 
			
		||||
    super(raw, Status)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def set_account
 | 
			
		||||
    @account = Account.find(params[:id])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def set_first_last_page(statuses) # rubocop:disable Style/AccessorMethodName
 | 
			
		||||
    return if statuses.empty?
 | 
			
		||||
 | 
			
		||||
    @first_page_url = api_activitypub_outbox_url(max_id: statuses.first.id + 1)
 | 
			
		||||
    @last_page_url = api_activitypub_outbox_url(since_id: statuses.last.id - 1)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def pagination_params(core_params)
 | 
			
		||||
    params.permit(:local, :limit).merge(core_params)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Api::PushController < Api::BaseController
 | 
			
		||||
  include SignatureVerification
 | 
			
		||||
 | 
			
		||||
  def update
 | 
			
		||||
    response, status = process_push_request
 | 
			
		||||
    render plain: response, status: status
 | 
			
		||||
@@ -11,7 +13,7 @@ class Api::PushController < Api::BaseController
 | 
			
		||||
  def process_push_request
 | 
			
		||||
    case hub_mode
 | 
			
		||||
    when 'subscribe'
 | 
			
		||||
      Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds)
 | 
			
		||||
      Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
 | 
			
		||||
    when 'unsubscribe'
 | 
			
		||||
      Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
 | 
			
		||||
    else
 | 
			
		||||
@@ -57,6 +59,10 @@ class Api::PushController < Api::BaseController
 | 
			
		||||
    TagManager.instance.web_domain?(hub_topic_domain)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def verified_domain
 | 
			
		||||
    return signed_request_account.domain if signed_request_account
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def hub_topic_domain
 | 
			
		||||
    hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ class Api::SubscriptionsController < Api::BaseController
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def lease_seconds_or_default
 | 
			
		||||
    (params['hub.lease_seconds'] || 86_400).to_i.seconds
 | 
			
		||||
    (params['hub.lease_seconds'] || 1.day).to_i.seconds
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def set_account
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
 | 
			
		||||
 | 
			
		||||
    UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
 | 
			
		||||
 | 
			
		||||
    render json: @status, serializer: REST::StatusSerializer
 | 
			
		||||
    render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
 | 
			
		||||
    authorize status_for_destroy, :unreblog?
 | 
			
		||||
    RemovalWorker.perform_async(status_for_destroy.id)
 | 
			
		||||
 | 
			
		||||
    render json: @status, serializer: REST::StatusSerializer
 | 
			
		||||
    render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								app/controllers/api/web/push_subscriptions_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/controllers/api/web/push_subscriptions_controller.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Api::Web::PushSubscriptionsController < Api::BaseController
 | 
			
		||||
  respond_to :json
 | 
			
		||||
 | 
			
		||||
  before_action :require_user!
 | 
			
		||||
 | 
			
		||||
  def create
 | 
			
		||||
    params.require(:data).require(:endpoint)
 | 
			
		||||
    params.require(:data).require(:keys).require([:auth, :p256dh])
 | 
			
		||||
 | 
			
		||||
    active_session = current_session
 | 
			
		||||
 | 
			
		||||
    unless active_session.web_push_subscription.nil?
 | 
			
		||||
      active_session.web_push_subscription.destroy!
 | 
			
		||||
      active_session.update!(web_push_subscription: nil)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    web_subscription = ::Web::PushSubscription.create!(
 | 
			
		||||
      endpoint: params[:data][:endpoint],
 | 
			
		||||
      key_p256dh: params[:data][:keys][:p256dh],
 | 
			
		||||
      key_auth: params[:data][:keys][:auth]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    active_session.update!(web_push_subscription: web_subscription)
 | 
			
		||||
 | 
			
		||||
    render json: web_subscription.as_payload
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def update
 | 
			
		||||
    params.require([:id, :data])
 | 
			
		||||
 | 
			
		||||
    web_subscription = ::Web::PushSubscription.find(params[:id])
 | 
			
		||||
 | 
			
		||||
    web_subscription.update!(data: params[:data])
 | 
			
		||||
 | 
			
		||||
    render json: web_subscription.as_payload
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										87
									
								
								app/controllers/concerns/signature_verification.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								app/controllers/concerns/signature_verification.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Implemented according to HTTP signatures (Draft 6)
 | 
			
		||||
# <https://tools.ietf.org/html/draft-cavage-http-signatures-06>
 | 
			
		||||
module SignatureVerification
 | 
			
		||||
  extend ActiveSupport::Concern
 | 
			
		||||
 | 
			
		||||
  def signed_request?
 | 
			
		||||
    request.headers['Signature'].present?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def signed_request_account
 | 
			
		||||
    return @signed_request_account if defined?(@signed_request_account)
 | 
			
		||||
 | 
			
		||||
    unless signed_request?
 | 
			
		||||
      @signed_request_account = nil
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    raw_signature    = request.headers['Signature']
 | 
			
		||||
    signature_params = {}
 | 
			
		||||
 | 
			
		||||
    raw_signature.split(',').each do |part|
 | 
			
		||||
      parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
 | 
			
		||||
      next if parsed_parts.nil? || parsed_parts.size != 3
 | 
			
		||||
      signature_params[parsed_parts[1]] = parsed_parts[2]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if incompatible_signature?(signature_params)
 | 
			
		||||
      @signed_request_account = nil
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    account = ResolveRemoteAccountService.new.call(signature_params['keyId'].gsub(/\Aacct:/, ''))
 | 
			
		||||
 | 
			
		||||
    if account.nil?
 | 
			
		||||
      @signed_request_account = nil
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    signature             = Base64.decode64(signature_params['signature'])
 | 
			
		||||
    compare_signed_string = build_signed_string(signature_params['headers'])
 | 
			
		||||
 | 
			
		||||
    if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
 | 
			
		||||
      @signed_request_account = account
 | 
			
		||||
      @signed_request_account
 | 
			
		||||
    else
 | 
			
		||||
      @signed_request_account = nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def build_signed_string(signed_headers)
 | 
			
		||||
    signed_headers = 'date' if signed_headers.blank?
 | 
			
		||||
 | 
			
		||||
    signed_headers.split(' ').map do |signed_header|
 | 
			
		||||
      if signed_header == Request::REQUEST_TARGET
 | 
			
		||||
        "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
 | 
			
		||||
      else
 | 
			
		||||
        "#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
 | 
			
		||||
      end
 | 
			
		||||
    end.join("\n")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def matches_time_window?
 | 
			
		||||
    begin
 | 
			
		||||
      time_sent = DateTime.httpdate(request.headers['Date'])
 | 
			
		||||
    rescue ArgumentError
 | 
			
		||||
      return false
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    (Time.now.utc - time_sent).abs <= 30
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def to_header_name(name)
 | 
			
		||||
    name.split(/-/).map(&:capitalize).join('-')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def incompatible_signature?(signature_params)
 | 
			
		||||
    signature_params['keyId'].blank? ||
 | 
			
		||||
      signature_params['signature'].blank? ||
 | 
			
		||||
      signature_params['algorithm'].blank? ||
 | 
			
		||||
      signature_params['algorithm'] != 'rsa-sha256' ||
 | 
			
		||||
      !signature_params['keyId'].start_with?('acct:')
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -5,5 +5,25 @@ class FollowerAccountsController < ApplicationController
 | 
			
		||||
 | 
			
		||||
  def index
 | 
			
		||||
    @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html
 | 
			
		||||
 | 
			
		||||
      format.json do
 | 
			
		||||
        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def collection_presenter
 | 
			
		||||
    ActivityPub::CollectionPresenter.new(
 | 
			
		||||
      id: account_followers_url(@account),
 | 
			
		||||
      type: :ordered,
 | 
			
		||||
      current: account_followers_url(@account),
 | 
			
		||||
      size: @account.followers_count,
 | 
			
		||||
      items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) }
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -5,5 +5,25 @@ class FollowingAccountsController < ApplicationController
 | 
			
		||||
 | 
			
		||||
  def index
 | 
			
		||||
    @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html
 | 
			
		||||
 | 
			
		||||
      format.json do
 | 
			
		||||
        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def collection_presenter
 | 
			
		||||
    ActivityPub::CollectionPresenter.new(
 | 
			
		||||
      id: account_following_index_url(@account),
 | 
			
		||||
      type: :ordered,
 | 
			
		||||
      current: account_following_index_url(@account),
 | 
			
		||||
      size: @account.following_count,
 | 
			
		||||
      items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) }
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ class HomeController < ApplicationController
 | 
			
		||||
  def initial_state_params
 | 
			
		||||
    {
 | 
			
		||||
      settings: Web::Setting.find_by(user: current_user)&.data || {},
 | 
			
		||||
      push_subscription: current_account.user.web_push_subscription(current_session),
 | 
			
		||||
      current_account: current_account,
 | 
			
		||||
      token: current_session.token,
 | 
			
		||||
      admin: Account.find_local(Setting.site_contact_username),
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController
 | 
			
		||||
      :setting_delete_modal,
 | 
			
		||||
      :setting_auto_play_gif,
 | 
			
		||||
      :setting_system_font_ui,
 | 
			
		||||
      :setting_noindex,
 | 
			
		||||
      notification_emails: %i(follow follow_request reblog favourite mention digest),
 | 
			
		||||
      interactions: %i(must_be_follower must_be_following)
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,22 @@ class StatusesController < ApplicationController
 | 
			
		||||
  before_action :check_account_suspension
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    @ancestors   = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
 | 
			
		||||
    @descendants = cache_collection(@status.descendants(current_account), Status)
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html do
 | 
			
		||||
        @ancestors   = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
 | 
			
		||||
        @descendants = cache_collection(@status.descendants(current_account), Status)
 | 
			
		||||
 | 
			
		||||
    render 'stream_entries/show'
 | 
			
		||||
        render 'stream_entries/show'
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      format.json do
 | 
			
		||||
        render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def activity
 | 
			
		||||
    render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
class StreamEntriesController < ApplicationController
 | 
			
		||||
  include Authorization
 | 
			
		||||
  include SignatureVerification
 | 
			
		||||
 | 
			
		||||
  layout 'public'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,27 @@ class TagsController < ApplicationController
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    @tag      = Tag.find_by!(name: params[:id].downcase)
 | 
			
		||||
    @statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
 | 
			
		||||
    @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
 | 
			
		||||
    @statuses = cache_collection(@statuses, Status)
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html
 | 
			
		||||
 | 
			
		||||
      format.json do
 | 
			
		||||
        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def collection_presenter
 | 
			
		||||
    ActivityPub::CollectionPresenter.new(
 | 
			
		||||
      id: tag_url(@tag),
 | 
			
		||||
      type: :ordered,
 | 
			
		||||
      current: tag_url(@tag),
 | 
			
		||||
      size: @tag.statuses.count,
 | 
			
		||||
      items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user