forked from treehouse/mastodon
Merge pull request #1174 from ThibG/glitch-soc/merge-upstream
Merge upstream changessignup-info-prompt
commit
08325db62b
6
Gemfile
6
Gemfile
|
@ -62,7 +62,7 @@ gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
|
|||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||
gem 'nokogiri', '~> 1.10'
|
||||
gem 'nsa', '~> 0.2'
|
||||
gem 'oj', '~> 3.7'
|
||||
gem 'oj', '~> 3.8'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.11'
|
||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||
|
@ -112,7 +112,7 @@ group :production, :test do
|
|||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.25'
|
||||
gem 'capybara', '~> 3.26'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 1.9'
|
||||
gem 'microformats', '~> 4.1'
|
||||
|
@ -132,7 +132,7 @@ group :development do
|
|||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 0.72', require: false
|
||||
gem 'rubocop', '~> 0.73', require: false
|
||||
gem 'rubocop-rails', '~> 2.2', require: false
|
||||
gem 'brakeman', '~> 4.5', require: false
|
||||
gem 'bundler-audit', '~> 0.6', require: false
|
||||
|
|
24
Gemfile.lock
24
Gemfile.lock
|
@ -111,7 +111,7 @@ GEM
|
|||
bootsnap (1.4.4)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.5.1)
|
||||
browser (2.5.3)
|
||||
browser (2.6.1)
|
||||
builder (3.2.3)
|
||||
bullet (6.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -136,7 +136,7 @@ GEM
|
|||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.25.0)
|
||||
capybara (3.26.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
|
@ -163,7 +163,7 @@ GEM
|
|||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.4)
|
||||
css_parser (1.6.0)
|
||||
css_parser (1.7.0)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
derailed_benchmarks (1.3.6)
|
||||
|
@ -354,7 +354,7 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2018.0812)
|
||||
mimemagic (0.3.3)
|
||||
mini_mime (1.0.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
msgpack (1.2.10)
|
||||
|
@ -375,7 +375,7 @@ GEM
|
|||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.7.12)
|
||||
oj (3.8.0)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
|
@ -417,8 +417,8 @@ GEM
|
|||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.10.2)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer-rails (1.10.3)
|
||||
actionmailer (>= 3)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
pry (0.12.2)
|
||||
|
@ -505,7 +505,7 @@ GEM
|
|||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.5.0)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (1.5.1)
|
||||
regexp_parser (1.6.0)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
responders (2.4.1)
|
||||
|
@ -535,7 +535,7 @@ GEM
|
|||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.8.0)
|
||||
rubocop (0.72.0)
|
||||
rubocop (0.73.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
|
@ -671,7 +671,7 @@ DEPENDENCIES
|
|||
capistrano-rails (~> 1.4)
|
||||
capistrano-rbenv (~> 2.1)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.25)
|
||||
capybara (~> 3.26)
|
||||
charlock_holmes (~> 0.7.6)
|
||||
chewy (~> 5.0)
|
||||
cld3 (~> 3.2.4)
|
||||
|
@ -719,7 +719,7 @@ DEPENDENCIES
|
|||
nilsimsa!
|
||||
nokogiri (~> 1.10)
|
||||
nsa (~> 0.2)
|
||||
oj (~> 3.7)
|
||||
oj (~> 3.8)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-cas (~> 1.1)
|
||||
omniauth-saml (~> 1.10)
|
||||
|
@ -752,7 +752,7 @@ DEPENDENCIES
|
|||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.8)
|
||||
rspec-sidekiq (~> 3.0)
|
||||
rubocop (~> 0.72)
|
||||
rubocop (~> 0.73)
|
||||
rubocop-rails (~> 2.2)
|
||||
sanitize (~> 5.0)
|
||||
sidekiq (~> 5.2)
|
||||
|
|
14
Procfile
14
Procfile
|
@ -1,2 +1,14 @@
|
|||
web: bundle exec puma -C config/puma.rb
|
||||
web: if [ "$RUN_STREAMING" != "true" ]; then BIND=0.0.0.0 bundle exec puma -C config/puma.rb; else BIND=0.0.0.0 node ./streaming; fi
|
||||
worker: bundle exec sidekiq
|
||||
|
||||
# For the streaming API, you need a separate app that shares Postgres and Redis:
|
||||
#
|
||||
# heroku create
|
||||
# heroku buildpacks:add heroku/nodejs
|
||||
# heroku config:set RUN_STREAMING=true
|
||||
# heroku addons:attach <main-app>::DATABASE
|
||||
# heroku addons:attach <main-app>::REDIS
|
||||
#
|
||||
# and let the main app use the separate app:
|
||||
#
|
||||
# heroku config:set STREAMING_API_BASE_URL=wss://<streaming-app>.herokuapp.com -a <main-app>
|
||||
|
|
|
@ -8,7 +8,7 @@ class AboutController < ApplicationController
|
|||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in
|
||||
|
||||
skip_before_action :check_user_permissions, only: [:more, :terms]
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
|
||||
def show; end
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class AccountsController < ApplicationController
|
|||
|
||||
format.json do
|
||||
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
|
||||
render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
|
||||
def show
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,7 +7,7 @@ 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 :set_cache_headers
|
||||
|
||||
|
|
|
@ -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).includes(:category), each_serializer: REST::CustomEmojiSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,8 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
|||
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
|
||||
|
|
|
@ -7,7 +7,8 @@ class Api::V1::Instances::PeersController < Api::BaseController
|
|||
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
|
||||
|
|
|
@ -5,8 +5,7 @@ class Api::V1::InstancesController < Api::BaseController
|
|||
skip_before_action :set_cache_headers
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
|
|||
include Localized
|
||||
include UserTrackingConcern
|
||||
include SessionTrackingConcern
|
||||
include CacheConcern
|
||||
|
||||
helper_method :current_account
|
||||
helper_method :current_session
|
||||
|
@ -25,7 +26,7 @@ class ApplicationController < ActionController::Base
|
|||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||
|
||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||
before_action :check_user_permissions, if: :user_signed_in?
|
||||
before_action :require_functional!, if: :user_signed_in?
|
||||
|
||||
def raise_not_found
|
||||
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
|
||||
|
@ -57,8 +58,8 @@ class ApplicationController < ActionController::Base
|
|||
forbidden unless current_user&.staff?
|
||||
end
|
||||
|
||||
def check_user_permissions
|
||||
forbidden if current_user.disabled? || current_user.account.suspended?
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional?
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
|
@ -190,52 +191,14 @@ class ApplicationController < ActionController::Base
|
|||
current_user.setting_skin
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any { head code }
|
||||
|
||||
format.html do
|
||||
set_locale
|
||||
use_pack 'error'
|
||||
render "errors/#{code}", layout: 'error', status: code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_cached_json(cache_key, **options)
|
||||
options[:expires_in] ||= 3.minutes
|
||||
cache_public = options.key?(:public) ? options.delete(:public) : true
|
||||
content_type = options.delete(:content_type) || 'application/json'
|
||||
|
||||
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||
yield.to_json
|
||||
end
|
||||
|
||||
expires_in options[:expires_in], public: cache_public
|
||||
render json: data, content_type: content_type
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,20 +4,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||
layout 'auth'
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :set_user, only: [:finish_signup]
|
||||
before_action :set_pack
|
||||
|
||||
def finish_signup
|
||||
return unless request.patch? && params[:user]
|
||||
|
||||
if @user.update(user_params)
|
||||
@user.skip_reconfirmation!
|
||||
bypass_sign_in(@user)
|
||||
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
@show_errors = true
|
||||
end
|
||||
end
|
||||
skip_before_action :require_functional!
|
||||
|
||||
private
|
||||
|
||||
|
@ -25,18 +14,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||
use_pack 'auth'
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def after_confirmation_path_for(_resource_name, user)
|
||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||
user.created_by_application.redirect_uri
|
||||
|
|
|
@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
if resource.email_verified?
|
||||
root_path
|
||||
else
|
||||
finish_signup_path
|
||||
auth_setup_path(missing_email: '1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
super(&:build_invite_request)
|
||||
|
@ -44,7 +47,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
new_user_session_path
|
||||
auth_setup_path
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
|
@ -107,4 +110,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
def set_sessions
|
||||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,9 +6,11 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
layout 'auth'
|
||||
|
||||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :check_user_permissions, only: [:destroy]
|
||||
skip_before_action :require_functional!
|
||||
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
prepend_before_action :set_pack
|
||||
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Auth::SetupController < ApplicationController
|
||||
layout 'auth'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_unconfirmed_or_pending!
|
||||
before_action :set_body_classes
|
||||
before_action :set_user
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
flash.now[:notice] = begin
|
||||
if @user.pending?
|
||||
I18n.t('devise.registrations.signed_up_but_pending')
|
||||
else
|
||||
I18n.t('devise.registrations.signed_up_but_unconfirmed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# This allows updating the e-mail without entering a password as is required
|
||||
# on the account settings page; however, we only allow this for accounts
|
||||
# that were not confirmed yet
|
||||
|
||||
if @user.update(user_params)
|
||||
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :missing_email?
|
||||
|
||||
private
|
||||
|
||||
def require_unconfirmed_or_pending!
|
||||
redirect_to root_path if current_user.confirmed? && current_user.approved?
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def missing_email?
|
||||
truthy_param?(:missing_email)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CacheConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def render_with_cache(**options)
|
||||
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
|
||||
body = Rails.cache.read(key, raw: true)
|
||||
|
||||
if body
|
||||
render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
|
||||
else
|
||||
if block_given?
|
||||
options[:json] = yield
|
||||
elsif options[:json].is_a?(Symbol)
|
||||
options[:json] = send(options[:json])
|
||||
end
|
||||
|
||||
render(options)
|
||||
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
|
||||
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)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
end
|
|
@ -4,16 +4,19 @@ module Localized
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_locale
|
||||
around_action :set_locale
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_locale
|
||||
I18n.locale = default_locale
|
||||
I18n.locale = current_user.locale if user_signed_in?
|
||||
rescue I18n::InvalidLocale
|
||||
I18n.locale = default_locale
|
||||
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
|
||||
locale ||= session[:locale] ||= default_locale
|
||||
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
|
||||
|
||||
I18n.with_locale(locale) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def default_locale
|
||||
|
|
|
@ -8,7 +8,7 @@ class EmojisController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
expires_in 3.minutes, public: true
|
||||
render json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
before_action :set_pack
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
include Localized
|
||||
|
||||
def destroy
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::ApplicationsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
|
||||
before_action :prepare_scopes, only: [:create, :update]
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::BaseController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_pack
|
||||
before_action :set_body_classes
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
use_pack 'settings'
|
||||
end
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::DeletesController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
prepend_before_action :check_enabled_deletion
|
||||
before_action :check_enabled_deletion
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::DeleteConfirmation.new
|
||||
|
@ -27,4 +32,8 @@ class Settings::DeletesController < Settings::BaseController
|
|||
def delete_params
|
||||
params.require(:form_delete_confirmation).permit(:password)
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
class Settings::ExportsController < Settings::BaseController
|
||||
include Authorization
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
@export = Export.new(current_account)
|
||||
@backups = current_user.backups
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::FlavoursController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def index
|
||||
redirect_to action: 'show', flavour: current_flavour
|
||||
end
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::ImportsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::MigrationsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
@migration = Form::Migration.new(account: current_account.moved_to_account)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::PreferencesController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
class Settings::ProfilesController < Settings::BaseController
|
||||
include ObfuscateFilename
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
obfuscate_filename [:account, :avatar]
|
||||
|
|
|
@ -5,6 +5,8 @@ class Settings::SessionsController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
before_action :set_session, only: :destroy
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def destroy
|
||||
@session.destroy!
|
||||
flash[:notice] = I18n.t('sessions.revoke_success')
|
||||
|
|
|
@ -3,8 +3,13 @@
|
|||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class ConfirmationsController < BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :ensure_otp_secret
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
prepare_two_factor_form
|
||||
end
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class RecoveryCodesController < BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def create
|
||||
@recovery_codes = current_user.generate_otp_backup_codes!
|
||||
current_user.save!
|
||||
|
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
module Settings
|
||||
class TwoFactorAuthenticationsController < BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :verify_otp_required, only: [:create]
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::TwoFactorConfirmation.new
|
||||
end
|
||||
|
|
|
@ -34,14 +34,14 @@ class StatusesController < ApplicationController
|
|||
|
||||
format.json do
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
|
||||
def embed
|
||||
|
|
|
@ -22,7 +22,7 @@ export function normalizeAccount(account) {
|
|||
if (account.fields) {
|
||||
account.fields = account.fields.map(pair => ({
|
||||
...pair,
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
|
||||
value_emojified: emojify(pair.value, emojiMap),
|
||||
value_plain: unescapeHTML(pair.value),
|
||||
}));
|
||||
|
|
|
@ -1,18 +1,64 @@
|
|||
// Package imports.
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
export default class DisplayName extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
className: PropTypes.string,
|
||||
inline: PropTypes.bool,
|
||||
localDomain: PropTypes.string,
|
||||
others: ImmutablePropTypes.list,
|
||||
handleClick: PropTypes.func,
|
||||
};
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, className, inline, localDomain, others, onAccountClick } = this.props;
|
||||
|
||||
// The component.
|
||||
export default function DisplayName ({
|
||||
account,
|
||||
className,
|
||||
inline,
|
||||
localDomain,
|
||||
others,
|
||||
onAccountClick,
|
||||
}) {
|
||||
const computedClass = classNames('display-name', { inline }, className);
|
||||
|
||||
if (!account) return null;
|
||||
|
@ -54,20 +100,12 @@ export default function DisplayName ({
|
|||
}
|
||||
|
||||
return (
|
||||
<span className={computedClass}>
|
||||
<span className={computedClass} ref={this.setRef}>
|
||||
{displayName}
|
||||
{inline ? ' ' : null}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Props.
|
||||
DisplayName.propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
className: PropTypes.string,
|
||||
inline: PropTypes.bool,
|
||||
localDomain: PropTypes.string,
|
||||
others: ImmutablePropTypes.list,
|
||||
handleClick: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { isRtl } from 'flavours/glitch/util/rtl';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import Permalink from './permalink';
|
||||
import classnames from 'classnames';
|
||||
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
export default class StatusContent extends React.PureComponent {
|
||||
|
||||
|
@ -57,12 +58,35 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
_updateStatusEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
if (this.props.onUpdate) this.props.onUpdate();
|
||||
}
|
||||
|
||||
|
@ -86,6 +110,14 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
this.startXY = [e.clientX, e.clientY];
|
||||
}
|
||||
|
@ -194,7 +226,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} ref={this.setRef}>
|
||||
<p
|
||||
style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}
|
||||
>
|
||||
|
@ -209,7 +241,6 @@ export default class StatusContent extends React.PureComponent {
|
|||
|
||||
<div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
|
||||
<div
|
||||
ref={this.setRef}
|
||||
style={directionStyle}
|
||||
tabIndex={!hidden ? 0 : null}
|
||||
dangerouslySetInnerHTML={content}
|
||||
|
@ -229,9 +260,9 @@ export default class StatusContent extends React.PureComponent {
|
|||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
tabIndex='0'
|
||||
ref={this.setRef}
|
||||
>
|
||||
<div
|
||||
ref={this.setRef}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.get('language')}
|
||||
className='status__content__text'
|
||||
|
@ -246,8 +277,9 @@ export default class StatusContent extends React.PureComponent {
|
|||
className='status__content'
|
||||
style={directionStyle}
|
||||
tabIndex='0'
|
||||
ref={this.setRef}
|
||||
>
|
||||
<div ref={this.setRef} className='status__content__text' dangerouslySetInnerHTML={content} lang={status.get('language')} tabIndex='0' />
|
||||
<div className='status__content__text' dangerouslySetInnerHTML={content} lang={status.get('language')} tabIndex='0' />
|
||||
{media}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -71,6 +71,47 @@ class Header extends ImmutablePureComponent {
|
|||
window.open('/settings/profile', '_blank');
|
||||
}
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain, identity_proofs } = this.props;
|
||||
|
||||
|
@ -193,7 +234,7 @@ class Header extends ImmutablePureComponent {
|
|||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })}>
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}>
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{info}
|
||||
|
|
|
@ -4,6 +4,7 @@ import ready from 'flavours/glitch/util/ready';
|
|||
function main() {
|
||||
const IntlMessageFormat = require('intl-messageformat').default;
|
||||
const { timeAgoString } = require('flavours/glitch/components/relative_timestamp');
|
||||
const { delegate } = require('rails-ujs');
|
||||
const emojify = require('flavours/glitch/util/emoji').default;
|
||||
const { getLocale } = require('locales');
|
||||
const { messages } = getLocale();
|
||||
|
@ -23,6 +24,12 @@ function main() {
|
|||
}
|
||||
};
|
||||
|
||||
const getEmojiAnimationHandler = (swapTo) => {
|
||||
return ({ target }) => {
|
||||
target.src = target.getAttribute(swapTo);
|
||||
};
|
||||
};
|
||||
|
||||
ready(() => {
|
||||
const locale = document.documentElement.lang;
|
||||
|
||||
|
@ -94,6 +101,9 @@ function main() {
|
|||
document.head.appendChild(scrollbarWidthStyle);
|
||||
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
|
||||
}
|
||||
|
||||
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
|
||||
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
|
|||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||
if (shortname in customEmojis) {
|
||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
||||
replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
|
||||
replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -22,7 +22,7 @@ export function normalizeAccount(account) {
|
|||
if (account.fields) {
|
||||
account.fields = account.fields.map(pair => ({
|
||||
...pair,
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
|
||||
value_emojified: emojify(pair.value, emojiMap),
|
||||
value_plain: unescapeHTML(pair.value),
|
||||
}));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
export default class DisplayName extends React.PureComponent {
|
||||
|
||||
|
@ -10,6 +11,47 @@ export default class DisplayName extends React.PureComponent {
|
|||
localDomain: PropTypes.string,
|
||||
};
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { others, localDomain } = this.props;
|
||||
|
||||
|
@ -39,7 +81,7 @@ export default class DisplayName extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<span className='display-name'>
|
||||
<span className='display-name' ref={this.setRef}>
|
||||
{displayName} {suffix}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ import Permalink from './permalink';
|
|||
import classnames from 'classnames';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
||||
|
||||
|
@ -71,12 +72,35 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
_updateStatusEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
|
@ -95,6 +119,14 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
this.startXY = [e.clientX, e.clientY];
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||
|
|
|
@ -79,6 +79,47 @@ class Header extends ImmutablePureComponent {
|
|||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
||||
}
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain, identity_proofs } = this.props;
|
||||
|
||||
|
@ -200,7 +241,7 @@ class Header extends ImmutablePureComponent {
|
|||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })}>
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}>
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{info}
|
||||
|
|
|
@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
|
|||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||
if (shortname in customEmojis) {
|
||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
||||
replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
|
||||
replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -27,6 +27,12 @@ function main() {
|
|||
}
|
||||
};
|
||||
|
||||
const getEmojiAnimationHandler = (swapTo) => {
|
||||
return ({ target }) => {
|
||||
target.src = target.getAttribute(swapTo);
|
||||
};
|
||||
};
|
||||
|
||||
ready(() => {
|
||||
const locale = document.documentElement.lang;
|
||||
|
||||
|
@ -91,6 +97,9 @@ function main() {
|
|||
if (parallaxComponents.length > 0 ) {
|
||||
new Rellax('.parallax', { speed: -1 });
|
||||
}
|
||||
|
||||
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
|
||||
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -204,29 +204,6 @@ $content-width: 840px;
|
|||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.muted-hint {
|
||||
color: $darker-text-color;
|
||||
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.positive-hint {
|
||||
color: $valid-value-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.negative-hint {
|
||||
color: $error-value-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.neutral-hint {
|
||||
color: $dark-text-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
|
@ -249,6 +226,41 @@ $content-width: 840px;
|
|||
}
|
||||
}
|
||||
|
||||
hr.spacer {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 20px 0;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.muted-hint {
|
||||
color: $darker-text-color;
|
||||
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.positive-hint {
|
||||
color: $valid-value-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.negative-hint {
|
||||
color: $error-value-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.neutral-hint {
|
||||
color: $dark-text-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.warning-hint {
|
||||
color: $gold-star;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -300,6 +300,13 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
.input.static .label_input__wrapper {
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border: 1px solid $dark-text-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=number],
|
||||
input[type=email],
|
||||
|
|
|
@ -200,11 +200,7 @@ class Formatter
|
|||
def encode_custom_emojis(html, emojis, animate = false)
|
||||
return html if emojis.empty?
|
||||
|
||||
emoji_map = if animate
|
||||
emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url) }
|
||||
else
|
||||
emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url(:static)) }
|
||||
end
|
||||
emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
|
||||
|
||||
i = -1
|
||||
tag_open_index = nil
|
||||
|
@ -220,7 +216,14 @@ class Formatter
|
|||
emoji = emoji_map[shortcode]
|
||||
|
||||
if emoji
|
||||
replacement = "<img draggable=\"false\" class=\"emojione\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(emoji)}\" />"
|
||||
original_url, static_url = emoji
|
||||
replacement = begin
|
||||
if animate
|
||||
"<img draggable=\"false\" class=\"emojione\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(original_url)}\" />"
|
||||
else
|
||||
"<img draggable=\"false\" class=\"emojione custom-emoji\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(static_url)}\" data-original=\"#{original_url}\" data-static=\"#{static_url}\" />"
|
||||
end
|
||||
end
|
||||
before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
|
||||
html = before_html + replacement + html[i + 1..-1]
|
||||
i += replacement.size - (shortcode.size + 2) - 1
|
||||
|
|
|
@ -31,9 +31,6 @@ module AccountAssociations
|
|||
has_many :media_attachments, dependent: :destroy
|
||||
has_many :polls, dependent: :destroy
|
||||
|
||||
# PuSH subscriptions
|
||||
has_many :subscriptions, dependent: :destroy
|
||||
|
||||
# Report relationships
|
||||
has_many :reports, dependent: :destroy, inverse_of: :account
|
||||
has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||
|
|
|
@ -43,7 +43,7 @@ module Omniauthable
|
|||
# Check if the user exists with provided email if the provider gives us a
|
||||
# verified email. If no verified email was provided or the user already
|
||||
# exists, we assign a temporary email and ask the user to verify it on
|
||||
# the next step via Auth::ConfirmationsController.finish_signup
|
||||
# the next step via Auth::SetupController.show
|
||||
|
||||
user = User.new(user_params_from_auth(auth))
|
||||
user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: subscriptions
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# callback_url :string default(""), not null
|
||||
# secret :string
|
||||
# expires_at :datetime
|
||||
# confirmed :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# last_successful_delivery_at :datetime
|
||||
# domain :string
|
||||
# account_id :bigint(8) not null
|
||||
#
|
||||
|
||||
class Subscription < ApplicationRecord
|
||||
MIN_EXPIRATION = 1.day.to_i
|
||||
MAX_EXPIRATION = 30.days.to_i
|
||||
|
||||
belongs_to :account
|
||||
|
||||
validates :callback_url, presence: true
|
||||
validates :callback_url, uniqueness: { scope: :account_id }
|
||||
|
||||
scope :confirmed, -> { where(confirmed: true) }
|
||||
scope :future_expiration, -> { where(arel_table[:expires_at].gt(Time.now.utc)) }
|
||||
scope :expired, -> { where(arel_table[:expires_at].lt(Time.now.utc)) }
|
||||
scope :active, -> { confirmed.future_expiration }
|
||||
|
||||
def lease_seconds=(value)
|
||||
self.expires_at = future_expiration(value)
|
||||
end
|
||||
|
||||
def lease_seconds
|
||||
(expires_at - Time.now.utc).to_i
|
||||
end
|
||||
|
||||
def expired?
|
||||
Time.now.utc > expires_at
|
||||
end
|
||||
|
||||
before_validation :set_min_expiration
|
||||
|
||||
private
|
||||
|
||||
def future_expiration(value)
|
||||
Time.now.utc + future_offset(value).seconds
|
||||
end
|
||||
|
||||
def future_offset(seconds)
|
||||
[
|
||||
[MIN_EXPIRATION, seconds.to_i].max,
|
||||
MAX_EXPIRATION,
|
||||
].min
|
||||
end
|
||||
|
||||
def set_min_expiration
|
||||
self.lease_seconds = 0 unless expires_at
|
||||
end
|
||||
end
|
|
@ -161,7 +161,11 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def active_for_authentication?
|
||||
super && approved?
|
||||
true
|
||||
end
|
||||
|
||||
def functional?
|
||||
confirmed? && approved? && !disabled? && !account.suspended?
|
||||
end
|
||||
|
||||
def inactive_message
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SubscriptionPolicy < ApplicationPolicy
|
||||
def index?
|
||||
admin?
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer
|
|||
attributes :id, :endpoint, :alerts, :server_key
|
||||
|
||||
def alerts
|
||||
object.data&.dig('alerts') || {}
|
||||
(object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) }
|
||||
end
|
||||
|
||||
def server_key
|
||||
|
|
|
@ -24,7 +24,6 @@ class SuspendAccountService < BaseService
|
|||
report_notes
|
||||
scheduled_statuses
|
||||
status_pins
|
||||
subscriptions
|
||||
).freeze
|
||||
|
||||
ASSOCIATIONS_ON_DESTROY = %w(
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
- content_for :page_title do
|
||||
= t('auth.confirm_email')
|
||||
|
||||
= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f|
|
||||
- if @show_errors && current_user.errors.any?
|
||||
#error_explanation
|
||||
- current_user.errors.full_messages.each do |msg|
|
||||
= msg
|
||||
%br
|
||||
|
||||
.fields-group
|
||||
= f.input :email, wrapper: :with_label, required: true, hint: false
|
||||
|
||||
.actions
|
||||
= f.submit t('auth.confirm_email'), class: 'button'
|
|
@ -1,6 +1,8 @@
|
|||
%h4= t 'sessions.title'
|
||||
%h3= t 'sessions.title'
|
||||
%p.muted-hint= t 'sessions.explanation'
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
.table-wrapper
|
||||
%table.table.inline-table
|
||||
%thead
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
%h3= t('auth.status.account_status')
|
||||
|
||||
- if @user.account.suspended?
|
||||
%span.negative-hint= t('user_mailer.warning.explanation.suspend')
|
||||
- elsif @user.disabled?
|
||||
%span.negative-hint= t('user_mailer.warning.explanation.disable')
|
||||
- elsif @user.account.silenced?
|
||||
%span.warning-hint= t('user_mailer.warning.explanation.silence')
|
||||
- elsif !@user.confirmed?
|
||||
%span.warning-hint= t('auth.status.confirming')
|
||||
- elsif !@user.approved?
|
||||
%span.warning-hint= t('auth.status.pending')
|
||||
- else
|
||||
%span.positive-hint= t('auth.status.functional')
|
||||
|
||||
%hr.spacer/
|
|
@ -1,25 +1,28 @@
|
|||
- content_for :page_title do
|
||||
= t('auth.security')
|
||||
= t('settings.account_settings')
|
||||
|
||||
= render 'status'
|
||||
|
||||
%h3= t('auth.security')
|
||||
|
||||
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
|
||||
= render 'shared/error_messages', object: resource
|
||||
|
||||
- if !use_seamless_external_login? || resource.encrypted_password.present?
|
||||
.fields-group
|
||||
= f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, hint: false
|
||||
|
||||
.fields-group
|
||||
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true
|
||||
|
||||
.fields-group
|
||||
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: false
|
||||
|
||||
.fields-group
|
||||
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
= f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
|
||||
- else
|
||||
%p.hint= t('users.seamless_external_login')
|
||||
|
||||
|
@ -27,7 +30,7 @@
|
|||
|
||||
= render 'sessions'
|
||||
|
||||
- if open_deletion?
|
||||
- if open_deletion? && !current_account.suspended?
|
||||
%hr.spacer/
|
||||
%h4= t('auth.delete_account')
|
||||
%h3= t('auth.delete_account')
|
||||
%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
- content_for :page_title do
|
||||
= t('auth.setup.title')
|
||||
|
||||
- if missing_email?
|
||||
= simple_form_for(@user, url: auth_setup_path) do |f|
|
||||
= render 'shared/error_messages', object: @user
|
||||
|
||||
.fields-group
|
||||
%p.hint= t('auth.setup.email_below_hint_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :email, required: true, hint: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.submit t('admin.accounts.change_email.label'), class: 'button'
|
||||
- else
|
||||
.simple_form
|
||||
%p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email))
|
||||
|
||||
.form-footer
|
||||
%ul.no-list
|
||||
%li= link_to t('settings.account_settings'), edit_user_registration_path
|
||||
%li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete }
|
|
@ -17,7 +17,7 @@
|
|||
= application.name
|
||||
- else
|
||||
= link_to application.name, application.website, target: '_blank', rel: 'noopener'
|
||||
%th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />')
|
||||
%th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ')
|
||||
%td= l application.created_at
|
||||
%td
|
||||
- unless application.superapp?
|
||||
|
|
|
@ -5,7 +5,5 @@ class Scheduler::SubscriptionsCleanupScheduler
|
|||
|
||||
sidekiq_options unique: :until_executed, retry: 0
|
||||
|
||||
def perform
|
||||
Subscription.expired.in_batches.delete_all
|
||||
end
|
||||
def perform; end
|
||||
end
|
||||
|
|
|
@ -114,6 +114,9 @@ module Mastodon
|
|||
Doorkeeper::AuthorizationsController.layout 'modal'
|
||||
Doorkeeper::AuthorizedApplicationsController.layout 'admin'
|
||||
Doorkeeper::Application.send :include, ApplicationExtension
|
||||
Devise::FailureApp.send :include, AbstractController::Callbacks
|
||||
Devise::FailureApp.send :include, HttpAcceptLanguage::EasyAccess
|
||||
Devise::FailureApp.send :include, Localized
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -537,7 +537,6 @@ en:
|
|||
apply_for_account: Request an invite
|
||||
change_password: Password
|
||||
checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
|
||||
confirm_email: Confirm email
|
||||
delete_account: Delete account
|
||||
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
|
||||
didnt_get_confirmation: Didn't receive confirmation instructions?
|
||||
|
@ -557,6 +556,14 @@ en:
|
|||
reset_password: Reset password
|
||||
security: Security
|
||||
set_new_password: Set new password
|
||||
setup:
|
||||
email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail.
|
||||
email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings.
|
||||
title: Setup
|
||||
status:
|
||||
account_status: Account status
|
||||
confirming: Waiting for e-mail confirmation to be completed.
|
||||
pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved.
|
||||
trouble_logging_in: Trouble logging in?
|
||||
authorize_follow:
|
||||
already_following: You are already following this account
|
||||
|
|
|
@ -34,7 +34,10 @@ Rails.application.routes.draw do
|
|||
|
||||
devise_scope :user do
|
||||
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
|
||||
match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup
|
||||
|
||||
namespace :auth do
|
||||
resource :setup, only: [:show, :update], controller: :setup
|
||||
end
|
||||
end
|
||||
|
||||
devise_for :users, path: 'auth', controllers: {
|
||||
|
|
|
@ -21,9 +21,6 @@
|
|||
user_cleanup_scheduler:
|
||||
cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
|
||||
class: Scheduler::UserCleanupScheduler
|
||||
subscriptions_cleanup_scheduler:
|
||||
cron: '<%= Random.rand(0..59) %> <%= Random.rand(1..3) %> * * 0'
|
||||
class: Scheduler::SubscriptionsCleanupScheduler
|
||||
ip_cleanup_scheduler:
|
||||
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
|
||||
class: Scheduler::IpCleanupScheduler
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropSubscriptions < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
drop_table :subscriptions
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -661,19 +661,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_164535) do
|
|||
t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
|
||||
end
|
||||
|
||||
create_table "subscriptions", force: :cascade do |t|
|
||||
t.string "callback_url", default: "", null: false
|
||||
t.string "secret"
|
||||
t.datetime "expires_at"
|
||||
t.boolean "confirmed", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "last_successful_delivery_at"
|
||||
t.string "domain"
|
||||
t.bigint "account_id", null: false
|
||||
t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true
|
||||
end
|
||||
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.string "name", default: "", null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
@ -836,7 +823,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_164535) do
|
|||
add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade
|
||||
add_foreign_key "statuses_tags", "statuses", on_delete: :cascade
|
||||
add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
|
||||
add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade
|
||||
add_foreign_key "tombstones", "accounts", on_delete: :cascade
|
||||
add_foreign_key "user_invite_requests", "users", on_delete: :cascade
|
||||
add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow')
|
||||
Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
|
||||
|
||||
domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
|
||||
Account.create!(id: -99, actor_type: 'Application', locked: true, username: domain)
|
||||
account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain)
|
||||
account.save!
|
||||
|
||||
if Rails.env.development?
|
||||
admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"@babel/preset-env": "^7.4.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.5.4",
|
||||
"@clusterws/cws": "^0.14.0",
|
||||
"@clusterws/cws": "^0.15.0",
|
||||
"array-includes": "^3.0.3",
|
||||
"atrament": "^0.2.3",
|
||||
"autoprefixer": "^9.6.0",
|
||||
|
@ -106,7 +106,7 @@
|
|||
"intersection-observer": "^0.7.0",
|
||||
"intl": "^1.2.5",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"intl-relativeformat": "^6.4.2",
|
||||
"intl-relativeformat": "^6.4.3",
|
||||
"is-nan": "^1.2.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lodash": "^4.17.14",
|
||||
|
@ -169,11 +169,11 @@
|
|||
"websocket.js": "^0.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-jest": "^24.8.0",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-plugin-import": "~2.18.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.2.3",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
|
|
|
@ -15,7 +15,7 @@ describe Api::BaseController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Forgery protection' do
|
||||
describe 'forgery protection' do
|
||||
before do
|
||||
routes.draw { post 'success' => 'api/base#success' }
|
||||
end
|
||||
|
@ -27,7 +27,45 @@ describe Api::BaseController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Error handling' do
|
||||
describe 'non-functional accounts handling' do
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||
|
||||
controller do
|
||||
before_action :require_user!
|
||||
end
|
||||
|
||||
before do
|
||||
routes.draw { post 'success' => 'api/base#success' }
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
it 'returns http forbidden for unconfirmed accounts' do
|
||||
user.update(confirmed_at: nil)
|
||||
post 'success'
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
|
||||
it 'returns http forbidden for pending accounts' do
|
||||
user.update(approved: false)
|
||||
post 'success'
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
|
||||
it 'returns http forbidden for disabled accounts' do
|
||||
user.update(disabled: true)
|
||||
post 'success'
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
|
||||
it 'returns http forbidden for suspended accounts' do
|
||||
user.account.suspend!
|
||||
post 'success'
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'error handling' do
|
||||
ERRORS_WITH_CODES = {
|
||||
ActiveRecord::RecordInvalid => 422,
|
||||
Mastodon::ValidationError => 422,
|
||||
|
|
|
@ -191,10 +191,10 @@ describe ApplicationController, type: :controller do
|
|||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns http 403 if user who signed in is suspended' do
|
||||
it 'redirects to account status page' do
|
||||
sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true)))
|
||||
get 'success'
|
||||
expect(response).to have_http_status(403)
|
||||
expect(response).to redirect_to(edit_user_registration_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -50,45 +50,4 @@ describe Auth::ConfirmationsController, type: :controller do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #finish_signup' do
|
||||
subject { get :finish_signup }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
before do
|
||||
sign_in user, scope: :user
|
||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
||||
end
|
||||
|
||||
it 'renders finish_signup' do
|
||||
is_expected.to render_template :finish_signup
|
||||
expect(assigns(:user)).to have_attributes id: user.id
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH #finish_signup' do
|
||||
subject { patch :finish_signup, params: { user: { email: email } } }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
before do
|
||||
sign_in user, scope: :user
|
||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
||||
end
|
||||
|
||||
context 'when email is valid' do
|
||||
let(:email) { 'new_' + user.email }
|
||||
|
||||
it 'redirects to root_path' do
|
||||
is_expected.to redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email is invalid' do
|
||||
let(:email) { '' }
|
||||
|
||||
it 'renders finish_signup' do
|
||||
is_expected.to render_template :finish_signup
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,15 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||
post :update
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'when suspended' do
|
||||
it 'returns http forbidden' do
|
||||
request.env["devise.mapping"] = Devise.mappings[:user]
|
||||
sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user)
|
||||
post :update
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
|
@ -94,9 +103,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
it 'redirects to setup' do
|
||||
subject
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
expect(response).to redirect_to auth_setup_path
|
||||
end
|
||||
|
||||
it 'creates user' do
|
||||
|
@ -120,9 +129,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
it 'redirects to setup' do
|
||||
subject
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
expect(response).to redirect_to auth_setup_path
|
||||
end
|
||||
|
||||
it 'creates user' do
|
||||
|
@ -148,9 +157,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
it 'redirects to setup' do
|
||||
subject
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
expect(response).to redirect_to auth_setup_path
|
||||
end
|
||||
|
||||
it 'creates user' do
|
||||
|
@ -176,9 +185,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
it 'redirects to setup' do
|
||||
subject
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
expect(response).to redirect_to auth_setup_path
|
||||
end
|
||||
|
||||
it 'creates user' do
|
||||
|
|
|
@ -160,8 +160,8 @@ RSpec.describe Auth::SessionsController, type: :controller do
|
|||
let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } }
|
||||
let(:accept_language) { 'fr' }
|
||||
|
||||
it 'shows a translated login error' do
|
||||
expect(flash[:alert]).to eq(I18n.t('devise.failure.unconfirmed', locale: accept_language))
|
||||
it 'redirects to home' do
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,16 +7,10 @@ describe ApplicationController, type: :controller do
|
|||
include Localized
|
||||
|
||||
def success
|
||||
head 200
|
||||
render plain: I18n.locale, status: 200
|
||||
end
|
||||
end
|
||||
|
||||
around do |example|
|
||||
current_locale = I18n.locale
|
||||
example.run
|
||||
I18n.locale = current_locale
|
||||
end
|
||||
|
||||
before do
|
||||
routes.draw { get 'success' => 'anonymous#success' }
|
||||
end
|
||||
|
@ -25,19 +19,19 @@ describe ApplicationController, type: :controller do
|
|||
it 'sets available and preferred language' do
|
||||
request.headers['Accept-Language'] = 'ca-ES, fa'
|
||||
get 'success'
|
||||
expect(I18n.locale).to eq :fa
|
||||
expect(response.body).to eq 'fa'
|
||||
end
|
||||
|
||||
it 'sets available and compatible language if none of available languages are preferred' do
|
||||
request.headers['Accept-Language'] = 'fa-IR'
|
||||
get 'success'
|
||||
expect(I18n.locale).to eq :fa
|
||||
expect(response.body).to eq 'fa'
|
||||
end
|
||||
|
||||
it 'sets default locale if none of available languages are compatible' do
|
||||
request.headers['Accept-Language'] = ''
|
||||
get 'success'
|
||||
expect(I18n.locale).to eq :en
|
||||
expect(response.body).to eq 'en'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -48,7 +42,7 @@ describe ApplicationController, type: :controller do
|
|||
sign_in(user)
|
||||
get 'success'
|
||||
|
||||
expect(I18n.locale).to eq :ca
|
||||
expect(response.body).to eq 'ca'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -15,6 +15,15 @@ describe Settings::DeletesController do
|
|||
get :show
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'when suspended' do
|
||||
let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
|
||||
|
||||
it 'returns http forbidden' do
|
||||
get :show
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
|
@ -49,6 +58,14 @@ describe Settings::DeletesController do
|
|||
it 'marks account as suspended' do
|
||||
expect(user.account.reload).to be_suspended
|
||||
end
|
||||
|
||||
context 'when suspended' do
|
||||
let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
|
||||
|
||||
it 'returns http forbidden' do
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with incorrect password' do
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
Fabricator(:subscription) do
|
||||
account
|
||||
callback_url "http://example.com/callback"
|
||||
secret "foobar"
|
||||
expires_at "2016-11-28 11:30:07"
|
||||
confirmed false
|
||||
end
|
|
@ -31,12 +31,12 @@ feature "Log in" do
|
|||
context do
|
||||
given(:confirmed_at) { nil }
|
||||
|
||||
scenario "A unconfirmed user is not able to log in" do
|
||||
scenario "A unconfirmed user is able to log in" do
|
||||
fill_in "user_email", with: email
|
||||
fill_in "user_password", with: password
|
||||
click_on I18n.t('auth.login')
|
||||
|
||||
is_expected.to have_css(".flash-message", text: failure_message("unconfirmed"))
|
||||
is_expected.to have_css("div.admin-wrapper")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { ':coolcat: Beep boop' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/<img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -330,7 +330,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { ':coolcat: Beep boop' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -338,7 +338,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { 'Beep :coolcat: boop' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -354,7 +354,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { 'Beep boop :coolcat:' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -377,7 +377,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>:coolcat: Beep boop<br />' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -385,7 +385,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>Beep :coolcat: boop</p>' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -401,7 +401,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -500,7 +500,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { ':coolcat: Beep boop' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -508,7 +508,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { 'Beep :coolcat: boop' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -524,7 +524,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { 'Beep boop :coolcat:' }
|
||||
|
||||
it 'converts the shortcode to an image tag' do
|
||||
is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -551,7 +551,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>:coolcat: Beep boop<br />' }
|
||||
|
||||
it 'converts shortcode to image tag' do
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -559,7 +559,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>Beep :coolcat: boop</p>' }
|
||||
|
||||
it 'converts shortcode to image tag' do
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -575,7 +575,7 @@ RSpec.describe Formatter do
|
|||
let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
|
||||
|
||||
it 'converts shortcode to image tag' do
|
||||
is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
|
||||
is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Subscription, type: :model do
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
|
||||
subject { Fabricate(:subscription, account: alice) }
|
||||
|
||||
describe '#expired?' do
|
||||
it 'return true when expires_at is past' do
|
||||
subject.expires_at = 2.days.ago
|
||||
expect(subject.expired?).to be true
|
||||
end
|
||||
|
||||
it 'return false when expires_at is future' do
|
||||
subject.expires_at = 2.days.from_now
|
||||
expect(subject.expired?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'lease_seconds' do
|
||||
it 'returns the time remaining until expiration' do
|
||||
datetime = 1.day.from_now
|
||||
subscription = Subscription.new(expires_at: datetime)
|
||||
travel_to(datetime - 12.hours) do
|
||||
expect(subscription.lease_seconds).to eq(12.hours)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'lease_seconds=' do
|
||||
it 'sets expires_at to min expiration when small value is provided' do
|
||||
subscription = Subscription.new
|
||||
datetime = 1.day.from_now
|
||||
too_low = Subscription::MIN_EXPIRATION - 1000
|
||||
travel_to(datetime) do
|
||||
subscription.lease_seconds = too_low
|
||||
end
|
||||
|
||||
expected = datetime + Subscription::MIN_EXPIRATION.seconds
|
||||
expect(subscription.expires_at).to be_within(1.0).of(expected)
|
||||
end
|
||||
|
||||
it 'sets expires_at to value when valid value is provided' do
|
||||
subscription = Subscription.new
|
||||
datetime = 1.day.from_now
|
||||
valid = Subscription::MIN_EXPIRATION + 1000
|
||||
travel_to(datetime) do
|
||||
subscription.lease_seconds = valid
|
||||
end
|
||||
|
||||
expected = datetime + valid.seconds
|
||||
expect(subscription.expires_at).to be_within(1.0).of(expected)
|
||||
end
|
||||
|
||||
it 'sets expires_at to max expiration when large value is provided' do
|
||||
subscription = Subscription.new
|
||||
datetime = 1.day.from_now
|
||||
too_high = Subscription::MAX_EXPIRATION + 1000
|
||||
travel_to(datetime) do
|
||||
subscription.lease_seconds = too_high
|
||||
end
|
||||
|
||||
expected = datetime + Subscription::MAX_EXPIRATION.seconds
|
||||
expect(subscription.expires_at).to be_within(1.0).of(expected)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -506,7 +506,7 @@ RSpec.describe User, type: :model do
|
|||
context 'when user is not confirmed' do
|
||||
let(:confirmed_at) { nil }
|
||||
|
||||
it { is_expected.to be false }
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -522,7 +522,7 @@ RSpec.describe User, type: :model do
|
|||
context 'when user is not confirmed' do
|
||||
let(:confirmed_at) { nil }
|
||||
|
||||
it { is_expected.to be false }
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require 'pundit/rspec'
|
||||
|
||||
RSpec.describe SubscriptionPolicy do
|
||||
let(:subject) { described_class }
|
||||
let(:admin) { Fabricate(:user, admin: true).account }
|
||||
let(:john) { Fabricate(:user).account }
|
||||
|
||||
permissions :index? do
|
||||
context 'admin?' do
|
||||
it 'permits' do
|
||||
expect(subject).to permit(admin, Subscription)
|
||||
end
|
||||
end
|
||||
|
||||
context '!admin?' do
|
||||
it 'denies' do
|
||||
expect(subject).to_not permit(john, Subscription)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,11 +14,8 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
|
|||
before do
|
||||
allow(Redis.current).to receive_messages(publish: nil)
|
||||
|
||||
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
||||
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
||||
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||
|
||||
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
||||
jeff.user.update(current_sign_in_at: Time.zone.now)
|
||||
jeff.follow!(alice)
|
||||
hank.follow!(alice)
|
||||
|
|
|
@ -10,12 +10,9 @@ RSpec.describe RemoveStatusService, type: :service do
|
|||
let!(:bill) { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') }
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
||||
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
||||
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||
stub_request(:post, 'http://example2.com/inbox').to_return(status: 200)
|
||||
|
||||
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
||||
jeff.follow!(alice)
|
||||
hank.follow!(alice)
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
let!(:favourite) { Fabricate(:favourite, account: account) }
|
||||
let!(:active_relationship) { Fabricate(:follow, account: account) }
|
||||
let!(:passive_relationship) { Fabricate(:follow, target_account: account) }
|
||||
let!(:subscription) { Fabricate(:subscription, account: account) }
|
||||
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
|
||||
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
|
||||
|
||||
|
@ -31,9 +30,8 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
account.favourites,
|
||||
account.active_relationships,
|
||||
account.passive_relationships,
|
||||
account.subscriptions
|
||||
].map(&:count)
|
||||
}.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
|
||||
}.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
|
||||
end
|
||||
|
||||
it 'sends a delete actor activity to all known inboxes' do
|
||||
|
@ -62,7 +60,6 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
let!(:favourite) { Fabricate(:favourite, account: remote_bob) }
|
||||
let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) }
|
||||
let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) }
|
||||
let!(:subscription) { Fabricate(:subscription, account: remote_bob) }
|
||||
|
||||
it 'deletes associated records' do
|
||||
is_expected.to change {
|
||||
|
@ -73,9 +70,8 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
remote_bob.favourites,
|
||||
remote_bob.active_relationships,
|
||||
remote_bob.passive_relationships,
|
||||
remote_bob.subscriptions
|
||||
].map(&:count)
|
||||
}.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
|
||||
}.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
|
||||
end
|
||||
|
||||
it 'sends a reject follow to follwer inboxes' do
|
||||
|
|
212
yarn.lock
212
yarn.lock
|
@ -60,7 +60,7 @@
|
|||
source-map "^0.5.0"
|
||||
trim-right "^1.0.1"
|
||||
|
||||
"@babel/generator@^7.2.2", "@babel/generator@^7.4.4":
|
||||
"@babel/generator@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
|
||||
integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
|
||||
|
@ -229,7 +229,7 @@
|
|||
"@babel/template" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4":
|
||||
"@babel/helper-split-export-declaration@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
|
||||
integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
|
||||
|
@ -273,17 +273,7 @@
|
|||
esutils "^2.0.2"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.0.0":
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
|
||||
integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
|
||||
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
|
||||
integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
|
||||
|
||||
"@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
|
||||
integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
|
||||
|
@ -793,22 +783,7 @@
|
|||
"@babel/parser" "^7.4.4"
|
||||
"@babel/types" "^7.4.4"
|
||||
|
||||
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.5":
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
|
||||
integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/generator" "^7.2.2"
|
||||
"@babel/helper-function-name" "^7.1.0"
|
||||
"@babel/helper-split-export-declaration" "^7.0.0"
|
||||
"@babel/parser" "^7.2.3"
|
||||
"@babel/types" "^7.2.2"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.10"
|
||||
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
|
||||
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
|
||||
integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
|
||||
|
@ -823,22 +798,7 @@
|
|||
globals "^11.1.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@babel/traverse@^7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
|
||||
integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/generator" "^7.3.4"
|
||||
"@babel/helper-function-name" "^7.1.0"
|
||||
"@babel/helper-split-export-declaration" "^7.0.0"
|
||||
"@babel/parser" "^7.3.4"
|
||||
"@babel/types" "^7.3.4"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.4.4":
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4", "@babel/types@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
|
||||
integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
|
||||
|
@ -847,28 +807,10 @@
|
|||
lodash "^4.17.11"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.0.0-beta.49":
|
||||
version "7.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
|
||||
integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.17.10"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.3.0", "@babel/types@^7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed"
|
||||
integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.17.11"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@clusterws/cws@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.14.0.tgz#242824b6884454001340222a836db6f6c5e62bfb"
|
||||
integrity sha512-knZj3KZNHIAGsX7TUc/0Q5gcx2bKMMcTPsAOZomLKdK5a4o/umKFlttWRH84Yr1nVlQy+UMO23qfDR8gRZ/4cw==
|
||||
"@clusterws/cws@^0.15.0":
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.15.0.tgz#1d585927252d1cd92e676c952fa6d69df14a0d07"
|
||||
integrity sha512-41QpCngw86n41hIdU5Nx2QJmmxZuA9FPtDkjONrYpk27L7HjL1kj6J5oWEjbr14yXLfigZit3VY+oACDCGbiHw==
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
|
@ -1423,10 +1365,10 @@ ajv@^4.7.0:
|
|||
co "^4.6.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1:
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
|
||||
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
|
||||
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
|
@ -1728,10 +1670,10 @@ axobject-query@^2.0.2:
|
|||
dependencies:
|
||||
ast-types-flow "0.0.7"
|
||||
|
||||
babel-eslint@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
||||
babel-eslint@^10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
|
@ -3709,7 +3651,7 @@ eslint-scope@3.7.1:
|
|||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^4.0.0, eslint-scope@^4.0.3:
|
||||
eslint-scope@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
|
||||
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
|
||||
|
@ -3717,6 +3659,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
|
|||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
||||
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-utils@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
|
||||
|
@ -3766,47 +3716,48 @@ eslint@^2.7.0:
|
|||
text-table "~0.2.0"
|
||||
user-home "^2.0.0"
|
||||
|
||||
eslint@^5.16.0:
|
||||
version "5.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
|
||||
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
|
||||
eslint@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.1.0.tgz#06438a4a278b1d84fb107d24eaaa35471986e646"
|
||||
integrity sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.9.1"
|
||||
ajv "^6.10.0"
|
||||
chalk "^2.1.0"
|
||||
cross-spawn "^6.0.5"
|
||||
debug "^4.0.1"
|
||||
doctrine "^3.0.0"
|
||||
eslint-scope "^4.0.3"
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^1.3.1"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
espree "^5.0.1"
|
||||
espree "^6.0.0"
|
||||
esquery "^1.0.1"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^5.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob "^7.1.2"
|
||||
glob-parent "^5.0.0"
|
||||
globals "^11.7.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^6.2.2"
|
||||
js-yaml "^3.13.0"
|
||||
inquirer "^6.4.1"
|
||||
is-glob "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.3.0"
|
||||
lodash "^4.17.11"
|
||||
lodash "^4.17.14"
|
||||
minimatch "^3.0.4"
|
||||
mkdirp "^0.5.1"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.8.2"
|
||||
path-is-inside "^1.0.2"
|
||||
progress "^2.0.0"
|
||||
regexpp "^2.0.1"
|
||||
semver "^5.5.1"
|
||||
strip-ansi "^4.0.0"
|
||||
strip-json-comments "^2.0.1"
|
||||
semver "^6.1.2"
|
||||
strip-ansi "^5.2.0"
|
||||
strip-json-comments "^3.0.1"
|
||||
table "^5.2.3"
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
espree@^3.1.6:
|
||||
version "3.5.4"
|
||||
|
@ -3816,10 +3767,10 @@ espree@^3.1.6:
|
|||
acorn "^5.5.0"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
espree@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
|
||||
integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==
|
||||
espree@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
|
||||
integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==
|
||||
dependencies:
|
||||
acorn "^6.0.7"
|
||||
acorn-jsx "^5.0.0"
|
||||
|
@ -4481,6 +4432,13 @@ glob-parent@^3.1.0:
|
|||
is-glob "^3.1.0"
|
||||
path-dirname "^1.0.0"
|
||||
|
||||
glob-parent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
|
||||
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
|
@ -5027,10 +4985,10 @@ inquirer@^0.12.0:
|
|||
strip-ansi "^3.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^6.2.2:
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b"
|
||||
integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==
|
||||
inquirer@^6.4.1:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
|
||||
integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
|
||||
dependencies:
|
||||
ansi-escapes "^3.2.0"
|
||||
chalk "^2.4.2"
|
||||
|
@ -5038,7 +4996,7 @@ inquirer@^6.2.2:
|
|||
cli-width "^2.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.17.11"
|
||||
lodash "^4.17.12"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rxjs "^6.4.0"
|
||||
|
@ -5093,10 +5051,10 @@ intl-relativeformat@^2.1.0:
|
|||
dependencies:
|
||||
intl-messageformat "^2.0.0"
|
||||
|
||||
intl-relativeformat@^6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.2.tgz#431f9818449f5b48c209610ff1428d0c663c667f"
|
||||
integrity sha512-yaOimRUQEn1wOfVGk43H+EVCrxQ5WFEvtYBx4Ffa6QpEHIi6UOuvshx6RltuqIF5UM8xdF4SkzFHXXOnYXlgBA==
|
||||
intl-relativeformat@^6.4.3:
|
||||
version "6.4.3"
|
||||
resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.3.tgz#cb5559e1e257cc2e763583502012a354bb777efe"
|
||||
integrity sha512-VxZXZfhuX/zBVfxzE/J6kPUpsyWKYjqtZ3jVGZwr6wzK5BOLVpe1vSlwCQX56w5UjlpL63fS8Nxq0kgTyf1gJA==
|
||||
|
||||
intl@^1.2.5:
|
||||
version "1.2.5"
|
||||
|
@ -5295,10 +5253,10 @@ is-glob@^3.1.0:
|
|||
dependencies:
|
||||
is-extglob "^2.1.0"
|
||||
|
||||
is-glob@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
|
||||
integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
|
||||
is-glob@^4.0.0, is-glob@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
|
@ -5896,7 +5854,7 @@ js-string-escape@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0:
|
||||
js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
||||
|
@ -6260,10 +6218,10 @@ lodash.uniq@^4.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10:
|
||||
version "4.17.14"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
|
||||
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
|
||||
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
loglevel@^1.6.3:
|
||||
version "1.6.3"
|
||||
|
@ -9024,15 +8982,10 @@ semver@4.3.2:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
|
||||
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
|
||||
|
||||
semver@^6.0.0:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.3.tgz#ef997a1a024f67dd48a7f155df88bb7b5c6c3fc7"
|
||||
integrity sha512-aymF+56WJJMyXQHcd4hlK4N75rwj5RQpfW8ePlQnJsTYOBLlLbcIErR/G1s9SkIvKBqOudR3KAx4wEqP+F1hNQ==
|
||||
|
||||
semver@^6.1.0, semver@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
|
||||
integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==
|
||||
semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
|
||||
integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
|
@ -9560,16 +9513,21 @@ strip-eof@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
|
||||
|
||||
strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
strip-json-comments@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
||||
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
|
||||
|
||||
strip-json-comments@~1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
||||
integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
stylehacks@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2"
|
||||
|
@ -10096,7 +10054,7 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
v8-compile-cache@2.0.3:
|
||||
v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
|
||||
|
|
Loading…
Reference in New Issue