Commit 8b2cad56 authored by Eugen Rochko's avatar Eugen Rochko Committed by GitHub

Refactor JSON templates to be generated with ActiveModelSerializers instead of Rabl (#4090)

parent 2d612867
......@@ -18,6 +18,7 @@ gem 'aws-sdk', '~> 2.9'
gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.5'
gem 'bootsnap'
gem 'browser'
......
......@@ -24,6 +24,11 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.10.6)
actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
active_record_query_trace (1.5.4)
activejob (5.1.2)
activesupport (= 5.1.2)
......@@ -101,6 +106,8 @@ GEM
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
case_transform (0.2)
activesupport
chunky_png (1.3.8)
cld3 (3.1.3)
ffi (>= 1.1.0, < 1.10.0)
......@@ -200,6 +207,7 @@ GEM
terminal-table (>= 1.5.1)
jmespath (1.3.1)
json (2.1.0)
jsonapi-renderer (0.1.2)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
......@@ -476,6 +484,7 @@ PLATFORMS
ruby
DEPENDENCIES
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.5)
addressable (~> 2.5)
annotate (~> 2.7)
......
......@@ -6,13 +6,13 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
def show
@account = current_account
render 'api/v1/accounts/show'
render json: @account, serializer: REST::AccountSerializer
end
def update
current_account.update!(account_params)
@account = current_account
render 'api/v1/accounts/show'
render json: @account, serializer: REST::AccountSerializer
end
private
......
......@@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
def index
@accounts = load_accounts
render 'api/v1/accounts/index'
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
def index
@accounts = load_accounts
render 'api/v1/accounts/index'
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -8,16 +8,15 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
def index
@accounts = Account.where(id: account_ids).select('id')
@following = Account.following_map(account_ids, current_user.account_id)
@followed_by = Account.followed_by_map(account_ids, current_user.account_id)
@blocking = Account.blocking_map(account_ids, current_user.account_id)
@muting = Account.muting_map(account_ids, current_user.account_id)
@requested = Account.requested_map(account_ids, current_user.account_id)
@domain_blocking = Account.domain_blocking_map(account_ids, current_user.account_id)
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end
private
def relationships
AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
end
def account_ids
@_account_ids ||= Array(params[:id]).map(&:to_i)
end
......
......@@ -8,8 +8,7 @@ class Api::V1::Accounts::SearchController < Api::BaseController
def show
@accounts = account_search
render 'api/v1/accounts/index'
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -9,6 +9,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
......@@ -18,9 +19,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def load_statuses
cached_account_statuses.tap do |statuses|
set_maps(statuses)
end
cached_account_statuses
end
def cached_account_statuses
......
......@@ -8,49 +8,38 @@ class Api::V1::AccountsController < Api::BaseController
respond_to :json
def show; end
def show
render json: @account, serializer: REST::AccountSerializer
end
def follow
FollowService.new.call(current_user.account, @account.acct)
set_relationship
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def block
BlockService.new.call(current_user.account, @account)
@following = { @account.id => false }
@followed_by = { @account.id => false }
@blocking = { @account.id => true }
@requested = { @account.id => false }
@muting = { @account.id => current_account.muting?(@account.id) }
@domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) }
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def mute
MuteService.new.call(current_user.account, @account)
set_relationship
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def unfollow
UnfollowService.new.call(current_user.account, @account)
set_relationship
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def unblock
UnblockService.new.call(current_user.account, @account)
set_relationship
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def unmute
UnmuteService.new.call(current_user.account, @account)
set_relationship
render :relationship
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
private
......@@ -59,12 +48,7 @@ class Api::V1::AccountsController < Api::BaseController
@account = Account.find(params[:id])
end
def set_relationship
@following = Account.following_map([@account.id], current_user.account_id)
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
@blocking = Account.blocking_map([@account.id], current_user.account_id)
@muting = Account.muting_map([@account.id], current_user.account_id)
@requested = Account.requested_map([@account.id], current_user.account_id)
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
def relationships
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
end
end
......@@ -5,6 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create
@app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer
end
private
......
......@@ -9,6 +9,7 @@ class Api::V1::BlocksController < Api::BaseController
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -9,14 +9,13 @@ class Api::V1::FavouritesController < Api::BaseController
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def load_statuses
cached_favourites.tap do |statuses|
set_maps(statuses)
end
cached_favourites
end
def cached_favourites
......
......@@ -7,6 +7,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
def authorize
......
......@@ -10,7 +10,7 @@ class Api::V1::FollowsController < Api::BaseController
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
render :show
render json: @account, serializer: REST::AccountSerializer
end
private
......
......@@ -3,5 +3,7 @@
class Api::V1::InstancesController < Api::BaseController
respond_to :json
def show; end
def show
render json: {}, serializer: REST::InstanceSerializer
end
end
......@@ -11,6 +11,7 @@ class Api::V1::MediaController < Api::BaseController
def create
@media = current_account.media_attachments.create!(file: media_params[:file])
render json: @media, serializer: REST::MediaAttachmentSerializer
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
......
......@@ -9,6 +9,7 @@ class Api::V1::MutesController < Api::BaseController
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -11,11 +11,12 @@ class Api::V1::NotificationsController < Api::BaseController
def index
@notifications = load_notifications
set_maps_for_notification_target_statuses
render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
end
def show
@notification = current_account.notifications.find(params[:id])
render json: @notification, serializer: REST::NotificationSerializer
end
def clear
......@@ -46,10 +47,6 @@ class Api::V1::NotificationsController < Api::BaseController
current_account.notifications.browserable(exclude_types)
end
def set_maps_for_notification_target_statuses
set_maps target_statuses_from_notifications
end
def target_statuses_from_notifications
@notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status)
end
......
......@@ -9,6 +9,7 @@ class Api::V1::ReportsController < Api::BaseController
def index
@reports = current_account.reports
render json: @reports, each_serializer: REST::ReportSerializer
end
def create
......@@ -20,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
render :show
render json: @report, serializer: REST::ReportSerializer
end
private
......
......@@ -6,7 +6,8 @@ class Api::V1::SearchController < Api::BaseController
respond_to :json
def index
@search = OpenStruct.new(search_results)
@search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer
end
private
......
......@@ -11,7 +11,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def index
@accounts = load_accounts
render 'api/v1/statuses/accounts'
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -10,7 +10,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
def create
@status = favourited_status
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
def destroy
......@@ -19,7 +19,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
private
......
......@@ -14,14 +14,14 @@ class Api::V1::Statuses::MutesController < Api::BaseController
current_account.mute_conversation!(@conversation)
@mutes_map = { @conversation.id => true }
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
def destroy
current_account.unmute_conversation!(@conversation)
@mutes_map = { @conversation.id => false }
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
private
......
......@@ -11,7 +11,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
def index
@accounts = load_accounts
render 'api/v1/statuses/accounts'
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
......
......@@ -10,7 +10,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
def create
@status = ReblogService.new.call(current_user.account, status_for_reblog)
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
def destroy
......@@ -20,7 +20,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
authorize status_for_destroy, :unreblog?
RemovalWorker.perform_async(status_for_destroy.id)
render 'api/v1/statuses/show'
render json: @status, serializer: REST::StatusSerializer
end
private
......
......@@ -13,6 +13,7 @@ class Api::V1::StatusesController < Api::BaseController
def show
cached = Rails.cache.read(@status.cache_key)
@status = cached unless cached.nil?
render json: @status, serializer: REST::StatusSerializer
end
def context
......@@ -21,15 +22,20 @@ class Api::V1::StatusesController < Api::BaseController
loaded_ancestors = cache_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status)
@context = OpenStruct.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context[:ancestors] + @context[:descendants]
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants
set_maps(statuses)
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
end
def card
@card = PreviewCard.find_by(status: @status)
render_empty if @card.nil?
if @card.nil?
render_empty
else
render json: @card, serializer: REST::PreviewCardSerializer
end
end
def create
......@@ -43,7 +49,7 @@ class Api::V1::StatusesController < Api::BaseController
application: doorkeeper_token.application,
idempotency: request.headers['Idempotency-Key'])
render :show
render json: @status, serializer: REST::StatusSerializer
end
def destroy
......
......@@ -9,15 +9,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController
def show
@statuses = load_statuses
render 'api/v1/timelines/show'
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def load_statuses
cached_home_statuses.tap do |statuses|
set_maps(statuses)
end
cached_home_statuses
end
def cached_home_statuses
......
......@@ -7,15 +7,13 @@ class Api::V1::Timelines::PublicController < Api::BaseController
def show
@statuses = load_statuses
render 'api/v1/timelines/show'
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def load_statuses
cached_public_statuses.tap do |statuses|
set_maps(statuses)
end
cached_public_statuses
end
def cached_public_statuses
......
......@@ -8,7 +8,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
def show
@statuses = load_statuses
render 'api/v1/timelines/show'
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
......@@ -18,9 +18,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end
def load_statuses
cached_tagged_statuses.tap do |statuses|
set_maps(statuses)
end
cached_tagged_statuses
end
def cached_tagged_statuses
......
# frozen_string_literal: true
class InlineRablScope
include RoutingHelper
def initialize(account)
@account = account
end
def current_user
@account.try(:user)
end
def current_account
@account
end
end
# frozen_string_literal: true
class InlineRenderer
def self.render(status, current_account, template)
Rabl::Renderer.new(
template,
status,
view_path: 'app/views',
format: :json,
scope: InlineRablScope.new(current_account)
).render
def initialize(object, current_account, template)
@object = object
@current_account = current_account
@template = template
end
def render
case @template
when :status
serializer = REST::StatusSerializer
when :notification
serializer = REST::NotificationSerializer
else
return
end
serializable_resource = ActiveModelSerializers::SerializableResource.new(@object, serializer: serializer, scope: current_user, scope_name: :current_user)
serializable_resource.as_json
end
def self.render(object, current_account, template)
new(object, current_account, template).render
end
private
def current_user
@current_account&.user
end
end
# frozen_string_literal: true
class Context < ActiveModelSerializers::Model
attributes :ancestors, :descendants
end
# frozen_string_literal: true
class Search < ActiveModelSerializers::Model
attributes :accounts, :statuses, :hashtags
end
# frozen_string_literal: true
class AccountRelationshipsPresenter
attr_reader :following, :followed_by, :blocking,
:muting, :requested, :domain_blocking
def initialize(account_ids, current_account_id)
@following = Account.following_map(account_ids, current_account_id)
@followed_by = Account.followed_by_map(account_ids, current_account_id)
@blocking = Account.blocking_map(account_ids, current_account_id)
@muting = Account.muting_map(account_ids, current_account_id)
@requested = Account.requested_map(account_ids, current_account_id)
@domain_blocking = Account.domain_blocking_map(account_ids, current_account_id)
end
end
# frozen_string_literal: true
class StatusRelationshipsPresenter
attr_reader :reblogs_map, :favourites_map, :mutes_map
def initialize(statuses, current_account_id = nil)
if current_account_id.nil?
@reblogs_map = {}
@favourites_map = {}
@mutes_map = {}
else
status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq
conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq
@reblogs_map = Status.reblogs_map(status_ids, current_account_id)
@favourites_map = Status.favourites_map(status_ids, current_account_id)
@mutes_map = Status.mutes_map(conversation_ids, current_account_id)
end
end
end
# frozen_string_literal: true
class REST::AccountSerializer < ActiveModel::Serializer
include RoutingHelper
attributes :id, :username, :acct, :display_name, :locked, :created_at,
:note, :url, :avatar, :avatar_static, :header, :header_static,
:followers_count, :following_count, :statuses_count
def note
Formatter.instance.simplified_format(object)
end
def url
TagManager.instance.url_for(object)
end
def avatar
full_asset_url(object.avatar_original_url)
end
def avatar_static
full_asset_url(object.avatar_static_url)
end
def header
full_asset_url(object.header_original_url)
end
def header_static
full_asset_url(object.header_static_url)
end
end
# frozen_string_literal: true
class REST::ApplicationSerializer < ActiveModel::Serializer
attributes :id, :name, :website, :redirect_uri,
:client_id, :client_secret
def client_id
object.uid
end
def client_secret
object.secret
end
end
# frozen_string_literal: true
class REST::ContextSerializer < ActiveModel::Serializer
has_many :ancestors, serializer: REST::StatusSerializer
has_many :descendants, serializer: REST::StatusSerializer
end