diff options
author | Starfall <root@starfall.blue> | 2019-12-09 19:07:33 -0600 |
---|---|---|
committer | Starfall <root@starfall.blue> | 2019-12-09 19:09:31 -0600 |
commit | 6b34fcfef7566105e8d80ab5fee0a539c06cddbf (patch) | |
tree | 8fad2d47bf8be255d3c671c40cbfd04c2f55ed03 /app/controllers/api | |
parent | 9fbb4af7611aa7836e65ef9f544d341423c15685 (diff) | |
parent | 246addd5b33a172600342af3fb6fb5e4c80ad95e (diff) |
Merge branch 'glitch'`
Diffstat (limited to 'app/controllers/api')
35 files changed, 545 insertions, 297 deletions
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index eca558f42..144fdd6ac 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -7,16 +7,23 @@ class Api::BaseController < ApplicationController include RateLimitHeaders skip_before_action :store_current_location - skip_before_action :check_user_permissions + skip_before_action :require_functional! + before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? before_action :set_cache_headers protect_from_forgery with: :null_session + skip_around_action :set_locale + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| render json: { error: e.to_s }, status: 422 end + rescue_from ActiveRecord::RecordNotUnique do + render json: { error: 'Duplicate record' }, status: 422 + end + rescue_from ActiveRecord::RecordNotFound do render json: { error: 'Record not found' }, status: 404 end @@ -33,6 +40,14 @@ class Api::BaseController < ApplicationController render json: { error: 'This action is not allowed' }, status: 403 end + rescue_from Mastodon::RaceConditionError do + render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503 + end + + rescue_from ActionController::ParameterMissing do |e| + render json: { error: e.to_s }, status: 400 + end + def doorkeeper_unauthorized_render_options(error: nil) { json: { error: (error.try(:description) || 'Not authorized') } } end @@ -69,6 +84,10 @@ class Api::BaseController < ApplicationController nil end + def require_authenticated_user! + render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user + end + def require_user! if !current_user render json: { error: 'This method requires an authenticated user' }, status: 422 @@ -94,4 +113,8 @@ class Api::BaseController < ApplicationController def set_cache_headers response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' end + + def disallow_unauthenticated_api_access? + authorized_fetch_mode? + end end diff --git a/app/controllers/api/proofs_controller.rb b/app/controllers/api/proofs_controller.rb index a84ad2014..dd32cd577 100644 --- a/app/controllers/api/proofs_controller.rb +++ b/app/controllers/api/proofs_controller.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true class Api::ProofsController < Api::BaseController - before_action :set_account + include AccountOwnedConcern + + skip_before_action :require_authenticated_user! + before_action :set_provider - before_action :check_account_approval - before_action :check_account_suspension def index render json: @account, serializer: @provider.serializer_class @@ -16,15 +17,7 @@ class Api::ProofsController < Api::BaseController @provider = ProofProvider.find(params[:provider]) || raise(ActiveRecord::RecordNotFound) end - def set_account - @account = Account.find_local!(params[:username]) - end - - def check_account_approval - not_found if @account.user_pending? - end - - def check_account_suspension - gone if @account.suspended? + def username_param + params[:username] end end diff --git a/app/controllers/api/push_controller.rb b/app/controllers/api/push_controller.rb deleted file mode 100644 index e04d19125..000000000 --- a/app/controllers/api/push_controller.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -class Api::PushController < Api::BaseController - include SignatureVerification - - def update - response, status = process_push_request - render plain: response, status: status - end - - private - - def process_push_request - case hub_mode - when 'subscribe' - 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 - ["Unknown mode: #{hub_mode}", 422] - end - end - - def hub_mode - params['hub.mode'] - end - - def hub_topic - params['hub.topic'] - end - - def hub_callback - params['hub.callback'] - end - - def hub_lease_seconds - params['hub.lease_seconds'] - end - - def hub_secret - params['hub.secret'] - end - - def account_from_topic - if hub_topic.present? && local_domain? && account_feed_path? - Account.find_local(hub_topic_params[:username]) - end - end - - def hub_topic_params - @_hub_topic_params ||= Rails.application.routes.recognize_path(hub_topic_uri.path) - end - - def hub_topic_uri - @_hub_topic_uri ||= Addressable::URI.parse(hub_topic).normalize - end - - def local_domain? - 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 - - def account_feed_path? - hub_topic_params[:controller] == 'accounts' && hub_topic_params[:action] == 'show' && hub_topic_params[:format] == 'atom' - end -end diff --git a/app/controllers/api/salmon_controller.rb b/app/controllers/api/salmon_controller.rb deleted file mode 100644 index ac5f3268d..000000000 --- a/app/controllers/api/salmon_controller.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -class Api::SalmonController < Api::BaseController - include SignatureVerification - - before_action :set_account - respond_to :txt - - def update - if verify_payload? - process_salmon - head 202 - elsif payload.present? - render plain: signature_verification_failure_reason, status: 401 - else - head 400 - end - end - - private - - def set_account - @account = Account.find(params[:id]) - end - - def payload - @_payload ||= request.body.read - end - - def verify_payload? - payload.present? && VerifySalmonService.new.call(payload) - end - - def process_salmon - SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8')) - end -end diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb deleted file mode 100644 index 89007f3d6..000000000 --- a/app/controllers/api/subscriptions_controller.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -class Api::SubscriptionsController < Api::BaseController - before_action :set_account - respond_to :txt - - def show - if subscription.valid?(params['hub.topic']) - @account.update(subscription_expires_at: future_expires) - render plain: encoded_challenge, status: 200 - else - head 404 - end - end - - def update - if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE']) - ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8')) - end - - head 200 - end - - private - - def subscription - @_subscription ||= @account.subscription( - api_subscription_url(@account.id) - ) - end - - def body - @_body ||= request.body.read - end - - def encoded_challenge - HTMLEntities.new.encode(params['hub.challenge']) - end - - def future_expires - Time.now.utc + lease_seconds_or_default - end - - def lease_seconds_or_default - (params['hub.lease_seconds'] || 1.day).to_i.seconds - end - - def set_account - @account = Account.find(params[:id]) - end -end diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index e77f57910..64b5cb747 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -25,7 +25,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController end def user_settings_params - return nil unless params.key?(:source) + return nil if params[:source].blank? source_params = params.require(:source) diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 8cd8f8e79..333db9618 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -3,7 +3,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :set_account - after_action :insert_pagination_headers + + after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } respond_to :json @@ -28,14 +29,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def account_statuses statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses - statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) statuses.merge!(only_media_scope) if truthy_param?(:only_media) statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) statuses.merge!(hashtag_scope) if params[:tagged].present? - statuses + statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) end def permitted_account_statuses @@ -57,6 +57,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def pinned_scope + return Status.none if @account.blocking?(current_account) + @account.pinned_statuses end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index b0c62778e..d68d2715f 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController before_action :check_account_suspension, only: [:show] before_action :check_enabled_registrations, only: [:create] + skip_before_action :require_authenticated_user!, only: :create + respond_to :json def show @@ -31,7 +33,7 @@ class Api::V1::AccountsController < Api::BaseController def follow FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) - options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } + options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) end @@ -76,7 +78,7 @@ class Api::V1::AccountsController < Api::BaseController end def account_params - params.permit(:username, :email, :password, :agreement, :locale) + params.permit(:username, :email, :password, :agreement, :locale, :reason) end def check_enabled_registrations diff --git a/app/controllers/api/v1/admin/account_actions_controller.rb b/app/controllers/api/v1/admin/account_actions_controller.rb new file mode 100644 index 000000000..29c9b7107 --- /dev/null +++ b/app/controllers/api/v1/admin/account_actions_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Api::V1::Admin::AccountActionsController < Api::BaseController + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' } + before_action :require_staff! + before_action :set_account + + def create + account_action = Admin::AccountAction.new(resource_params) + account_action.target_account = @account + account_action.current_account = current_account + account_action.save! + + render_empty + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def resource_params + params.permit( + :type, + :report_id, + :warning_preset_id, + :text, + :send_email_notification + ) + end +end diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb new file mode 100644 index 000000000..c35ea5ab2 --- /dev/null +++ b/app/controllers/api/v1/admin/accounts_controller.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +class Api::V1::Admin::AccountsController < Api::BaseController + include Authorization + include AccountableConcern + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show] + before_action :require_staff! + before_action :set_accounts, only: :index + before_action :set_account, except: :index + before_action :require_local_account!, only: [:enable, :approve, :reject] + + after_action :insert_pagination_headers, only: :index + + FILTER_PARAMS = %i( + local + remote + by_domain + active + pending + disabled + silenced + suspended + username + display_name + email + ip + staff + ).freeze + + PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze + + def index + authorize :account, :index? + render json: @accounts, each_serializer: REST::Admin::AccountSerializer + end + + def show + authorize @account, :show? + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def enable + authorize @account.user, :enable? + @account.user.enable! + log_action :enable, @account.user + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def approve + authorize @account.user, :approve? + @account.user.approve! + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def reject + authorize @account.user, :reject? + SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false) + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def unsilence + authorize @account, :unsilence? + @account.unsilence! + log_action :unsilence, @account + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def unsuspend + authorize @account, :unsuspend? + @account.unsuspend! + log_action :unsuspend, @account + render json: @account, serializer: REST::Admin::AccountSerializer + end + + private + + def set_accounts + @accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def set_account + @account = Account.find(params[:id]) + end + + def filtered_accounts + AccountFilter.new(filter_params).results + end + + def filter_params + params.permit(*FILTER_PARAMS) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end + + def require_local_account! + forbidden unless @account.local? && @account.user.present? + end +end diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb new file mode 100644 index 000000000..1d48d3160 --- /dev/null +++ b/app/controllers/api/v1/admin/reports_controller.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +class Api::V1::Admin::ReportsController < Api::BaseController + include Authorization + include AccountableConcern + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show] + before_action :require_staff! + before_action :set_reports, only: :index + before_action :set_report, except: :index + + after_action :insert_pagination_headers, only: :index + + FILTER_PARAMS = %i( + resolved + account_id + target_account_id + ).freeze + + PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze + + def index + authorize :report, :index? + render json: @reports, each_serializer: REST::Admin::ReportSerializer + end + + def show + authorize @report, :show? + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def assign_to_self + authorize @report, :update? + @report.update!(assigned_account_id: current_account.id) + log_action :assigned_to_self, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def unassign + authorize @report, :update? + @report.update!(assigned_account_id: nil) + log_action :unassigned, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def reopen + authorize @report, :update? + @report.unresolve! + log_action :reopen, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def resolve + authorize @report, :update? + @report.resolve!(current_account) + log_action :resolve, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + private + + def set_reports + @reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def set_report + @report = Report.find(params[:id]) + end + + def filtered_reports + ReportFilter.new(filter_params).results + end + + def filter_params + params.permit(*FILTER_PARAMS) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_reports_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_reports_url(pagination_params(min_id: pagination_since_id)) unless @reports.empty? + end + + def pagination_max_id + @reports.last.id + end + + def pagination_since_id + @reports.first.id + end + + def records_continue? + @reports.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end +end diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb index e9f7a7291..97177547a 100644 --- a/app/controllers/api/v1/apps_controller.rb +++ b/app/controllers/api/v1/apps_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Api::V1::AppsController < Api::BaseController + skip_before_action :require_authenticated_user! + def create @app = Doorkeeper::Application.create!(application_options) render json: @app, serializer: REST::ApplicationSerializer diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index 1cab3c372..e1b244e76 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -26,10 +26,9 @@ class Api::V1::BookmarksController < Api::BaseController end def results - @_results ||= account_bookmarks.paginate_by_max_id( + @_results ||= account_bookmarks.paginate_by_id( limit_param(DEFAULT_STATUSES_LIMIT), - params[:max_id], - params[:since_id] + params_slice(:max_id, :since_id, :min_id) ) end @@ -42,15 +41,11 @@ class Api::V1::BookmarksController < Api::BaseController end def next_path - if records_continue? - api_v1_bookmarks_url pagination_params(max_id: pagination_max_id) - end + api_v1_bookmarks_url pagination_params(max_id: pagination_max_id) if records_continue? end def prev_path - unless results.empty? - api_v1_bookmarks_url pagination_params(since_id: pagination_since_id) - end + api_v1_bookmarks_url pagination_params(min_id: pagination_since_id) unless results.empty? end def pagination_max_id diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 1bb19a09d..4e6d5d7c6 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController skip_before_action :set_cache_headers def index - render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do - ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer) - end + expires_in 3.minutes, public: true + render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) } end end diff --git a/app/controllers/api/v1/directories_controller.rb b/app/controllers/api/v1/directories_controller.rb new file mode 100644 index 000000000..c91543e3a --- /dev/null +++ b/app/controllers/api/v1/directories_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::DirectoriesController < Api::BaseController + before_action :require_enabled! + before_action :set_accounts + + def show + render json: @accounts, each_serializer: REST::AccountSerializer + end + + private + + def require_enabled! + return not_found unless Setting.profile_directory + end + + def set_accounts + @accounts = accounts_scope.offset(params[:offset]).limit(limit_param(DEFAULT_ACCOUNTS_LIMIT)) + end + + def accounts_scope + Account.discoverable.tap do |scope| + scope.merge!(Account.local) if truthy_param?(:local) + scope.merge!(Account.by_recent_status) if params[:order].blank? || params[:order] == 'active' + scope.merge!(Account.order(id: :desc)) if params[:order] == 'new' + scope.merge!(Account.not_excluded_by_account(current_account)) if current_account + scope.merge!(Account.not_domain_blocked_by_account(current_account)) if current_account && !truthy_param?(:local) + end + end +end diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb new file mode 100644 index 000000000..fb27ef88b --- /dev/null +++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index + + before_action :require_user! + before_action :set_most_used_tags, only: :index + + respond_to :json + + def index + render json: @most_used_tags, each_serializer: REST::TagSerializer + end + + private + + def set_most_used_tags + @most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10) + end +end diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb new file mode 100644 index 000000000..e4e836c97 --- /dev/null +++ b/app/controllers/api/v1/featured_tags_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class Api::V1::FeaturedTagsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + + before_action :require_user! + before_action :set_featured_tags, only: :index + before_action :set_featured_tag, except: [:index, :create] + + def index + render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer + end + + def create + @featured_tag = current_account.featured_tags.new(featured_tag_params) + @featured_tag.reset_data + @featured_tag.save! + render json: @featured_tag, serializer: REST::FeaturedTagSerializer + end + + def destroy + @featured_tag.destroy! + render_empty + end + + private + + def set_featured_tag + @featured_tag = current_account.featured_tags.find(params[:id]) + end + + def set_featured_tags + @featured_tags = current_account.featured_tags.order(statuses_count: :desc) + end + + def featured_tag_params + params.permit(:name) + end +end diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index e6888154e..0ee6e531f 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController def authorize AuthorizeFollowService.new.call(account, current_account) NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account)) - render_empty + render json: account, serializer: REST::RelationshipSerializer, relationships: relationships end def reject RejectFollowService.new.call(account, current_account) - render_empty + render json: account, serializer: REST::RelationshipSerializer, relationships: relationships end private @@ -28,6 +28,10 @@ class Api::V1::FollowRequestsController < Api::BaseController Account.find(params[:id]) end + def relationships(**options) + AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options) + end + def load_accounts default_accounts.merge(paginated_follow_requests).to_a end diff --git a/app/controllers/api/v1/follows_controller.rb b/app/controllers/api/v1/follows_controller.rb deleted file mode 100644 index 5420c0533..000000000 --- a/app/controllers/api/v1/follows_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::FollowsController < Api::BaseController - before_action -> { doorkeeper_authorize! :follow, :'write:follows' } - before_action :require_user! - - respond_to :json - - def create - raise ActiveRecord::RecordNotFound if follow_params[:uri].blank? - - @account = FollowService.new.call(current_user.account, target_uri).try(:target_account) - - if @account.nil? - username, domain = target_uri.split('@') - @account = Account.find_remote!(username, domain) - end - - render json: @account, serializer: REST::AccountSerializer - end - - private - - def target_uri - follow_params[:uri].strip.gsub(/\A@/, '') - end - - def follow_params - params.permit(:uri) - end -end diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index 09edfe365..b30e8464c 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -2,12 +2,15 @@ class Api::V1::Instances::ActivityController < Api::BaseController before_action :require_enabled_api! + skip_before_action :set_cache_headers + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? respond_to :json def show - render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity } + expires_in 1.day, public: true + render_with_cache json: :activity, expires_in: 1.day end private @@ -32,6 +35,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController end def require_enabled_api! - head 404 unless Setting.activity_api_enabled + head 404 unless Setting.activity_api_enabled && !whitelist_mode? end end diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index a8891d126..cc00d8a6b 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -2,17 +2,20 @@ class Api::V1::Instances::PeersController < Api::BaseController before_action :require_enabled_api! + skip_before_action :set_cache_headers + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? respond_to :json def index - render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains } + expires_in 1.day, public: true + render_with_cache(expires_in: 1.day) { Account.remote.domains } end private def require_enabled_api! - head 404 unless Setting.peers_api_enabled + head 404 unless Setting.peers_api_enabled && !whitelist_mode? end end diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 8c83a1801..c323b60b4 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -2,11 +2,12 @@ class Api::V1::InstancesController < Api::BaseController respond_to :json + skip_before_action :set_cache_headers + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? def show - render_cached_json('api:v1:instances', expires_in: 5.minutes) do - ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer) - end + expires_in 3.minutes, public: true + render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance' end end diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb new file mode 100644 index 000000000..28c2ec791 --- /dev/null +++ b/app/controllers/api/v1/markers_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class Api::V1::MarkersController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:index] + before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, except: [:index] + + before_action :require_user! + + def index + @markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker } + render json: serialize_map(@markers) + end + + def create + Marker.transaction do + @markers = {} + + resource_params.each_pair do |timeline, timeline_params| + @markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline) + @markers[timeline].update!(timeline_params) + end + end + + render json: serialize_map(@markers) + rescue ActiveRecord::StaleObjectError + render json: { error: 'Conflict during update, please try again' }, status: 409 + end + + private + + def serialize_map(map) + serialized = {} + + map.each_pair do |key, value| + serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json + end + + Oj.dump(serialized) + end + + def resource_params + params.slice(*Marker::TIMELINES).permit(*Marker::TIMELINES.map { |timeline| { timeline.to_sym => [:last_read_id] } }) + end +end diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1b658f870..1cbc92b93 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -51,6 +51,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController def data_params return {} if params[:data].blank? - params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) + params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index e182a9c6c..1b0b4b05b 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -21,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController private def reported_status_ids - reported_account.statuses.find(status_ids).pluck(:id) + reported_account.statuses.with_discarded.find(status_ids).pluck(:id) end def status_ids diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb deleted file mode 100644 index 6131cbbb6..000000000 --- a/app/controllers/api/v1/search_controller.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::SearchController < Api::BaseController - include Authorization - - RESULTS_LIMIT = 20 - - before_action -> { doorkeeper_authorize! :read, :'read:search' } - before_action :require_user! - - respond_to :json - - def index - @search = Search.new(search_results) - render json: @search, serializer: REST::SearchSerializer - end - - private - - def search_results - SearchService.new.call( - params[:q], - current_account, - limit_param(RESULTS_LIMIT), - search_params.merge(resolve: truthy_param?(:resolve)) - ) - end - - def search_params - params.permit(:type, :offset, :min_id, :max_id, :account_id) - end -end diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index ed4f55100..42381a37f 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -18,6 +18,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController @reblogs_map = { @status.id => false } authorize status_for_destroy, :unreblog? + status_for_destroy.discard RemovalWorker.perform_async(status_for_destroy.id) render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map) @@ -30,7 +31,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController end def status_for_destroy - current_user.account.statuses.where(reblog_of_id: params[:status_id]).first! + @status_for_destroy ||= current_user.account.statuses.where(reblog_of_id: params[:status_id]).first! end def reblog_params diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 26a0ab457..486004f9c 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy] - before_action :require_user!, except: [:show, :context, :card] - before_action :set_status, only: [:show, :context, :card] + before_action :require_user!, except: [:show, :context] + before_action :set_status, only: [:show, :context] respond_to :json @@ -33,16 +33,6 @@ class Api::V1::StatusesController < Api::BaseController render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) end - def card - @card = @status.preview_cards.first - - if @card.nil? - render_empty - else - render json: @card, serializer: REST::PreviewCardSerializer - end - end - def create @status = PostStatusService.new.call(current_user.account, text: status_params[:status], @@ -64,7 +54,8 @@ class Api::V1::StatusesController < Api::BaseController @status = Status.where(account_id: current_user.account).find(params[:id]) authorize @status, :destroy? - RemovalWorker.perform_async(@status.id) + @status.discard + RemovalWorker.perform_async(@status.id, redraft: true) render json: @status, serializer: REST::StatusSerializer, source_requested: true end diff --git a/app/controllers/api/v1/streaming_controller.rb b/app/controllers/api/v1/streaming_controller.rb index 66b812e76..ebb17608c 100644 --- a/app/controllers/api/v1/streaming_controller.rb +++ b/app/controllers/api/v1/streaming_controller.rb @@ -5,11 +5,17 @@ class Api::V1::StreamingController < Api::BaseController def index if Rails.configuration.x.streaming_api_base_url != request.host - uri = URI.parse(request.url) - uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host - redirect_to uri.to_s, status: 301 + redirect_to streaming_api_url, status: 301 else - raise ActiveRecord::RecordNotFound + not_found end end + + private + + def streaming_api_url + Addressable::URI.parse(request.url).tap do |uri| + uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host + end.to_s + end end diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb index d8a76d153..6e98e9cac 100644 --- a/app/controllers/api/v1/timelines/direct_controller.rb +++ b/app/controllers/api/v1/timelines/direct_controller.rb @@ -27,16 +27,18 @@ class Api::V1::Timelines::DirectController < Api::BaseController end def direct_timeline_statuses - # this query requires built in pagination. - Status.as_direct_timeline( - current_account, + account_direct_feed.get( limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id], - true # returns array of cache_ids object + params[:min_id] ) end + def account_direct_feed + DirectFeed.new(current_account) + end + def insert_pagination_headers set_pagination_headers(next_path, prev_path) end diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index fcd0757f1..ff5ede138 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -13,7 +13,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), - status: regeneration_in_progress? ? 206 : 200 + status: account_home_feed.regenerating? ? 206 : 200 end private @@ -62,8 +62,4 @@ class Api::V1::Timelines::HomeController < Api::BaseController def pagination_since_id @statuses.first.id end - - def regeneration_in_progress? - Redis.current.exists("account:#{current_account.id}:regeneration") - end end diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index aabe24324..ccc10f966 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::PublicController < Api::BaseController + before_action :require_user!, only: [:show], if: :require_auth? after_action :insert_pagination_headers, unless: -> { @statuses.empty? } respond_to :json @@ -12,6 +13,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController private + def require_auth? + !Setting.timeline_preview + end + def load_statuses cached_public_statuses end diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb new file mode 100644 index 000000000..bcea9857e --- /dev/null +++ b/app/controllers/api/v1/trends_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::V1::TrendsController < Api::BaseController + before_action :set_tags + + respond_to :json + + def index + render json: @tags, each_serializer: REST::TagSerializer + end + + private + + def set_tags + @tags = TrendingTags.get(limit_param(10)) + end +end diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index 9aa6edc69..76decdb25 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -1,8 +1,32 @@ # frozen_string_literal: true -class Api::V2::SearchController < Api::V1::SearchController +class Api::V2::SearchController < Api::BaseController + include Authorization + + RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i + + before_action -> { doorkeeper_authorize! :read, :'read:search' } + before_action :require_user! + + respond_to :json + def index @search = Search.new(search_results) - render json: @search, serializer: REST::V2::SearchSerializer + render json: @search, serializer: REST::SearchSerializer + end + + private + + def search_results + SearchService.new.call( + params[:q], + current_account, + limit_param(RESULTS_LIMIT), + search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed)) + ) + end + + def search_params + params.permit(:type, :offset, :min_id, :max_id, :account_id) end end diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index d8153e082..f388b17e5 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -19,6 +19,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController data = { alerts: { follow: alerts_enabled, + follow_request: false, favourite: alerts_enabled, reblog: alerts_enabled, mention: alerts_enabled, @@ -58,6 +59,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController end def data_params - @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) + @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end |