Merge branch 'kb_migration' into kb_migration_development
This commit is contained in:
commit
51cac0760f
99 changed files with 1077 additions and 365 deletions
|
@ -7,8 +7,9 @@ class AccountsController < ApplicationController
|
|||
include AccountControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
|
|
@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController
|
|||
|
||||
private
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
|
||||
end
|
||||
|
||||
def skip_temporary_suspension_response?
|
||||
false
|
||||
end
|
||||
|
|
|
@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_items
|
||||
before_action :set_size
|
||||
before_action :set_type
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
|
|
|
@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||
|
||||
before_action :require_account_signature!
|
||||
before_action :set_items
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 0, public: false
|
||||
|
|
|
@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? }
|
||||
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
if page_requested?
|
||||
|
@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
|||
else
|
||||
expires_in(3.minutes, public: public_fetch_mode?)
|
||||
end
|
||||
|
||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
|
@ -80,8 +82,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
|||
def set_account
|
||||
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
|||
|
||||
DESCENDANTS_LIMIT = 60
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies
|
||||
|
||||
def index
|
||||
|
|
|
@ -8,6 +8,8 @@ module Admin
|
|||
layout 'admin'
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
private
|
||||
|
@ -16,6 +18,10 @@ module Admin
|
|||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class Api::BaseController < ApplicationController
|
|||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :require_not_suspended!
|
||||
before_action :set_cache_headers
|
||||
before_action :set_cache_control_defaults
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
|
@ -148,8 +148,8 @@ class Api::BaseController < ApplicationController
|
|||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
def set_cache_control_defaults
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
LIMIT = 100
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
|
||||
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
|
||||
before_action :set_providers, only: :index
|
||||
|
||||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :preview_card_provider, :index?
|
||||
|
||||
render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize :preview_card_provider, :review?
|
||||
|
||||
provider = PreviewCardProvider.find(params[:id])
|
||||
provider.update(trendable: true, reviewed_at: Time.now.utc)
|
||||
render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize :preview_card_provider, :review?
|
||||
|
||||
provider = PreviewCardProvider.find(params[:id])
|
||||
provider.update(trendable: false, reviewed_at: Time.now.utc)
|
||||
render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_providers
|
||||
@providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@providers.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@providers.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@providers.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
|
@ -1,7 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
|
||||
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
|
||||
|
||||
after_action :verify_authorized, except: :index
|
||||
|
||||
def index
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize :preview_card, :review?
|
||||
|
||||
link = PreviewCard.find(params[:id])
|
||||
link.update(trendable: true)
|
||||
render json: link, serializer: REST::Admin::Trends::LinkSerializer
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize :preview_card, :review?
|
||||
|
||||
link = PreviewCard.find(params[:id])
|
||||
link.update(trendable: false)
|
||||
render json: link, serializer: REST::Admin::Trends::LinkSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -1,7 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
|
||||
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
|
||||
|
||||
after_action :verify_authorized, except: :index
|
||||
|
||||
def index
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize [:admin, :status], :review?
|
||||
|
||||
status = Status.find(params[:id])
|
||||
status.update(trendable: true)
|
||||
render json: status, serializer: REST::Admin::Trends::StatusSerializer
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize [:admin, :status], :review?
|
||||
|
||||
status = Status.find(params[:id])
|
||||
status.update(trendable: false)
|
||||
render json: status, serializer: REST::Admin::Trends::StatusSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
|
||||
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
|
||||
|
||||
after_action :verify_authorized, except: :index
|
||||
|
||||
def index
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
|
@ -11,6 +16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
|
|||
end
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize :tag, :review?
|
||||
|
||||
tag = Tag.find(params[:id])
|
||||
tag.update(trendable: true, reviewed_at: Time.now.utc)
|
||||
render json: tag, serializer: REST::Admin::TagSerializer
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize :tag, :review?
|
||||
|
||||
tag = Tag.find(params[:id])
|
||||
tag.update(trendable: false, reviewed_at: Time.now.utc)
|
||||
render json: tag, serializer: REST::Admin::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enabled?
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::CustomEmojisController < Api::BaseController
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
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?
|
||||
|
||||
def show
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
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?
|
||||
|
||||
def index
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::InstancesController < Api::BaseController
|
||||
skip_before_action :set_cache_headers
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
def show
|
||||
|
|
|
@ -152,6 +152,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -155,8 +155,16 @@ module CacheConcern
|
|||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def vary_by(value)
|
||||
before_action do |controller|
|
||||
response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||
expires_in = options.delete(:expires_in) || 3.minutes
|
||||
|
@ -176,10 +184,6 @@ module CacheConcern
|
|||
end
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
|
|
|
@ -1,18 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CustomCssController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :update_user_sign_in
|
||||
skip_before_action :set_session_activity
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
before_action :set_cache_headers
|
||||
|
||||
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
request.session_options[:skip] = true
|
||||
render content_type: 'text/css'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,10 +9,15 @@ class Disputes::BaseController < ApplicationController
|
|||
|
||||
before_action :set_body_classes
|
||||
before_action :authenticate_user!
|
||||
before_action :set_cache_headers
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,15 +2,12 @@
|
|||
|
||||
class EmojisController < ApplicationController
|
||||
before_action :set_emoji
|
||||
before_action :set_cache_headers
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,6 +7,7 @@ class Filters::StatusesController < ApplicationController
|
|||
before_action :set_filter
|
||||
before_action :set_status_filters
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
|
@ -44,4 +45,8 @@ class Filters::StatusesController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ class FiltersController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
before_action :set_filter, only: [:edit, :update, :destroy]
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
|
||||
|
@ -54,4 +55,8 @@ class FiltersController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController
|
|||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
|
|
@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController
|
|||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
|
|
@ -7,6 +7,7 @@ class InvitesController < ApplicationController
|
|||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
authorize :invite, :create?
|
||||
|
@ -49,4 +50,8 @@ class InvitesController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ManifestsController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'
|
||||
|
|
|
@ -34,6 +34,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
before_action :authenticate_resource_owner!
|
||||
before_action :require_not_suspended!, only: :destroy
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
|
@ -30,4 +31,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class RelationshipsController < ApplicationController
|
|||
before_action :set_accounts, only: :show
|
||||
before_action :set_relationships, only: :show
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
|
||||
|
||||
|
@ -70,4 +71,8 @@ class RelationshipsController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class Settings::BaseController < ApplicationController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
|
|
|
@ -6,6 +6,7 @@ class StatusesCleanupController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
before_action :set_policy
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show; end
|
||||
|
||||
|
@ -36,4 +37,8 @@ class StatusesCleanupController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,11 +6,12 @@ class StatusesController < ApplicationController
|
|||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
|
||||
|
||||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes, only: :embed
|
||||
|
||||
after_action :set_link_headers
|
||||
|
|
|
@ -7,6 +7,8 @@ class TagsController < ApplicationController
|
|||
PAGE_SIZE = 20
|
||||
PAGE_SIZE_MAX = 200
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_local
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WellKnown
|
||||
class HostMetaController < ActionController::Base
|
||||
class HostMetaController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
before_action { response.headers['Vary'] = 'Accept' }
|
||||
|
||||
def show
|
||||
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
||||
expires_in 3.days, public: true
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WellKnown
|
||||
class NodeInfoController < ActionController::Base
|
||||
class NodeInfoController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
include CacheConcern
|
||||
|
||||
before_action { response.headers['Vary'] = 'Accept' }
|
||||
|
||||
def index
|
||||
expires_in 3.days, public: true
|
||||
render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WellKnown
|
||||
class WebfingerController < ActionController::Base
|
||||
class WebfingerController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
before_action :set_account
|
||||
|
@ -34,7 +34,12 @@ module WellKnown
|
|||
end
|
||||
|
||||
def check_account_suspension
|
||||
expires_in(3.minutes, public: true) && gone if @account.suspended_permanently?
|
||||
gone if @account.suspended_permanently?
|
||||
end
|
||||
|
||||
def gone
|
||||
expires_in(3.minutes, public: true)
|
||||
head 410
|
||||
end
|
||||
|
||||
def bad_request
|
||||
|
@ -46,9 +51,5 @@ module WellKnown
|
|||
expires_in(3.minutes, public: true)
|
||||
head 404
|
||||
end
|
||||
|
||||
def gone
|
||||
head 410
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
||||
<div
|
||||
className="account__avatar-overlay"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": 46,
|
||||
|
@ -15,8 +17,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
|||
>
|
||||
<div
|
||||
className="account__avatar"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": "36px",
|
||||
|
@ -35,8 +35,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
|||
>
|
||||
<div
|
||||
className="account__avatar"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": "24px",
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { reduceMotion } from 'mastodon/initial_state';
|
||||
|
||||
const obfuscatedCount = count => {
|
||||
if (count < 0) {
|
||||
return 0;
|
||||
} else if (count <= 1) {
|
||||
return count;
|
||||
} else {
|
||||
return '1+';
|
||||
}
|
||||
};
|
||||
|
||||
export default class AnimatedNumber extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.number.isRequired,
|
||||
obfuscate: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
direction: 1,
|
||||
};
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.value > this.props.value) {
|
||||
this.setState({ direction: 1 });
|
||||
} else if (nextProps.value < this.props.value) {
|
||||
this.setState({ direction: -1 });
|
||||
}
|
||||
}
|
||||
|
||||
willEnter = () => {
|
||||
const { direction } = this.state;
|
||||
|
||||
return { y: -1 * direction };
|
||||
};
|
||||
|
||||
willLeave = () => {
|
||||
const { direction } = this.state;
|
||||
|
||||
return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) };
|
||||
};
|
||||
|
||||
render () {
|
||||
const { value, obfuscate } = this.props;
|
||||
const { direction } = this.state;
|
||||
|
||||
if (reduceMotion) {
|
||||
return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />;
|
||||
}
|
||||
|
||||
const styles = [{
|
||||
key: `${value}`,
|
||||
data: value,
|
||||
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
|
||||
}];
|
||||
|
||||
return (
|
||||
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
|
||||
{items => (
|
||||
<span className='animated-number'>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
58
app/javascript/mastodon/components/animated_number.tsx
Normal file
58
app/javascript/mastodon/components/animated_number.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import ShortNumber from './short_number';
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
import { reduceMotion } from '../initial_state';
|
||||
|
||||
const obfuscatedCount = (count: number) => {
|
||||
if (count < 0) {
|
||||
return 0;
|
||||
} else if (count <= 1) {
|
||||
return count;
|
||||
} else {
|
||||
return '1+';
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value: number;
|
||||
obfuscate?: boolean;
|
||||
}
|
||||
export const AnimatedNumber: React.FC<Props> = ({
|
||||
value,
|
||||
obfuscate,
|
||||
})=> {
|
||||
const [previousValue, setPreviousValue] = useState(value);
|
||||
const [direction, setDirection] = useState<1|-1>(1);
|
||||
|
||||
if (previousValue !== value) {
|
||||
setPreviousValue(value);
|
||||
setDirection(value > previousValue ? 1 : -1);
|
||||
}
|
||||
|
||||
const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
|
||||
const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]);
|
||||
|
||||
if (reduceMotion) {
|
||||
return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />;
|
||||
}
|
||||
|
||||
const styles = [{
|
||||
key: `${value}`,
|
||||
data: value,
|
||||
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
|
||||
}];
|
||||
|
||||
return (
|
||||
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
|
||||
{items => (
|
||||
<span className='animated-number'>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnimatedNumber;
|
|
@ -1,51 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import Avatar from './avatar';
|
||||
|
||||
export default class AvatarOverlay extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
friend: ImmutablePropTypes.map.isRequired,
|
||||
animate: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
baseSize: PropTypes.number,
|
||||
overlaySize: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: autoPlayGif,
|
||||
size: 46,
|
||||
baseSize: 36,
|
||||
overlaySize: 24,
|
||||
};
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
};
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (this.props.animate) return;
|
||||
this.setState({ hovering: true });
|
||||
};
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (this.props.animate) return;
|
||||
this.setState({ hovering: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account, friend, animate, size, baseSize, overlaySize } = this.props;
|
||||
const { hovering } = this.state;
|
||||
|
||||
return (
|
||||
<div className='account__avatar-overlay' style={{ width: size, height: size }}>
|
||||
<div className='account__avatar-overlay-base'><Avatar animate={hovering || animate} account={account} size={baseSize} /></div>
|
||||
<div className='account__avatar-overlay-overlay'><Avatar animate={hovering || animate} account={friend} size={overlaySize} /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
51
app/javascript/mastodon/components/avatar_overlay.tsx
Normal file
51
app/javascript/mastodon/components/avatar_overlay.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import type { Account } from '../../types/resources';
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
type Props = {
|
||||
account: Account;
|
||||
friend: Account;
|
||||
size?: number;
|
||||
baseSize?: number;
|
||||
overlaySize?: number;
|
||||
};
|
||||
|
||||
export const AvatarOverlay: React.FC<Props> = ({
|
||||
account,
|
||||
friend,
|
||||
size = 46,
|
||||
baseSize = 36,
|
||||
overlaySize = 24,
|
||||
}) => {
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif);
|
||||
const accountSrc = hovering ? account?.get('avatar') : account?.get('avatar_static');
|
||||
const friendSrc = hovering ? friend?.get('avatar') : friend?.get('avatar_static');
|
||||
|
||||
return (
|
||||
<div
|
||||
className='account__avatar-overlay' style={{ width: size, height: size }}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className='account__avatar-overlay-base'>
|
||||
<div
|
||||
className='account__avatar'
|
||||
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
|
||||
>
|
||||
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='account__avatar-overlay-overlay'>
|
||||
<div
|
||||
className='account__avatar'
|
||||
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
|
||||
>
|
||||
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvatarOverlay;
|
|
@ -32,17 +32,14 @@ function ShortNumber({ value, renderer, children }) {
|
|||
const shortNumber = toShortNumber(value);
|
||||
const [, division] = shortNumber;
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (children != null && renderer != null) {
|
||||
console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const customRenderer = children != null ? children : renderer;
|
||||
|
||||
const displayNumber = <ShortNumberCounter value={shortNumber} />;
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return customRenderer != null
|
||||
? customRenderer(displayNumber, pluralReady(value, division))
|
||||
: displayNumber;
|
||||
|
|
|
@ -131,4 +131,4 @@ class FilterModal extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(injectIntl(FilterModal));
|
||||
export default connect()(injectIntl(FilterModal));
|
||||
|
|
|
@ -60,7 +60,6 @@ export function toShortNumber(sourceNumber) {
|
|||
* // => 1790
|
||||
*/
|
||||
export function pluralReady(sourceNumber, division) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (division == null || division < DECIMAL_UNITS.HUNDRED) {
|
||||
return sourceNumber;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ class NotificationMailer < ApplicationMailer
|
|||
|
||||
locale_for_account(@me) do
|
||||
thread_by_conversation(@status.conversation)
|
||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
|
||||
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ class NotificationMailer < ApplicationMailer
|
|||
return unless @me.user.functional?
|
||||
|
||||
locale_for_account(@me) do
|
||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
|
||||
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@ class NotificationMailer < ApplicationMailer
|
|||
|
||||
locale_for_account(@me) do
|
||||
thread_by_conversation(@status.conversation)
|
||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
|
||||
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,7 +51,7 @@ class NotificationMailer < ApplicationMailer
|
|||
|
||||
locale_for_account(@me) do
|
||||
thread_by_conversation(@status.conversation)
|
||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
|
||||
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,7 +62,7 @@ class NotificationMailer < ApplicationMailer
|
|||
return unless @me.user.functional?
|
||||
|
||||
locale_for_account(@me) do
|
||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
|
||||
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class AccountFilter
|
|||
when 'by_domain'
|
||||
Account.where(domain: value.to_s.strip)
|
||||
when 'username'
|
||||
Account.matches_username(value.to_s.strip)
|
||||
Account.matches_username(value.to_s.strip.delete_prefix('@'))
|
||||
when 'display_name'
|
||||
Account.matches_display_name(value.to_s.strip)
|
||||
when 'email'
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#
|
||||
|
||||
class PreviewCardProvider < ApplicationRecord
|
||||
include Paginable
|
||||
include DomainNormalizable
|
||||
include Attachmentable
|
||||
|
||||
|
|
9
app/serializers/rest/admin/trends/link_serializer.rb
Normal file
9
app/serializers/rest/admin/trends/link_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer
|
||||
attributes :id, :requires_review
|
||||
|
||||
def requires_review
|
||||
object.requires_review?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer
|
||||
attributes :id, :domain, :trendable, :reviewed_at,
|
||||
:requested_review_at, :requires_review
|
||||
|
||||
def requires_review
|
||||
object.requires_review?
|
||||
end
|
||||
end
|
9
app/serializers/rest/admin/trends/status_serializer.rb
Normal file
9
app/serializers/rest/admin/trends/status_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer
|
||||
attributes :requires_review
|
||||
|
||||
def requires_review
|
||||
object.requires_review?
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ class NotifyService < BaseService
|
|||
admin.report
|
||||
admin.sign_up
|
||||
update
|
||||
poll
|
||||
).freeze
|
||||
|
||||
def call(recipient, type, activity)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue