mirror of https://github.com/mastodon/mastodon
Merge 0f084abb2b
into bdb6650ebc
This commit is contained in:
commit
f87b4f8261
2
Gemfile
2
Gemfile
|
@ -204,3 +204,5 @@ gem 'net-http', '~> 0.3.2'
|
||||||
gem 'rubyzip', '~> 2.3'
|
gem 'rubyzip', '~> 2.3'
|
||||||
|
|
||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
|
||||||
|
gem 'mail', '~> 2.8'
|
||||||
|
|
|
@ -288,7 +288,7 @@ GEM
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fast_blank (1.0.1)
|
fast_blank (1.0.1)
|
||||||
fastimage (2.2.7)
|
fastimage (2.3.1)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
|
@ -368,7 +368,7 @@ GEM
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.6.3)
|
json (2.6.3)
|
||||||
json-canonicalization (1.0.0)
|
json-canonicalization (1.0.0)
|
||||||
json-jwt (1.15.3)
|
json-jwt (1.15.3.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
aes_key_wrap
|
aes_key_wrap
|
||||||
bindata
|
bindata
|
||||||
|
@ -537,7 +537,7 @@ GEM
|
||||||
rack (2.2.8.1)
|
rack (2.2.8.1)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (2.0.1)
|
rack-cors (2.0.2)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
rack-oauth2 (1.21.3)
|
rack-oauth2 (1.21.3)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -606,7 +606,7 @@ GEM
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.2.6)
|
rexml (3.2.6)
|
||||||
rotp (6.2.2)
|
rotp (6.3.0)
|
||||||
rouge (4.1.2)
|
rouge (4.1.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
|
@ -871,6 +871,7 @@ DEPENDENCIES
|
||||||
letter_opener_web (~> 2.0)
|
letter_opener_web (~> 2.0)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.12)
|
lograge (~> 0.12)
|
||||||
|
mail (~> 2.8)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
md-paperclip-azure (~> 2.2)
|
md-paperclip-azure (~> 2.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
|
|
|
@ -25,6 +25,8 @@ class Admin::DomainAllowsController < Admin::BaseController
|
||||||
def destroy
|
def destroy
|
||||||
authorize @domain_allow, :destroy?
|
authorize @domain_allow, :destroy?
|
||||||
UnallowDomainService.new.call(@domain_allow)
|
UnallowDomainService.new.call(@domain_allow)
|
||||||
|
log_action :destroy, @domain_allow
|
||||||
|
|
||||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,11 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
def create
|
def create
|
||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
|
|
||||||
|
@domain_block = DomainBlock.new(resource_params)
|
||||||
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
||||||
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present?
|
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if conflicts_with_existing_block?(@domain_block, existing_domain_block)
|
||||||
|
|
||||||
@domain_block = DomainBlock.create!(resource_params)
|
@domain_block.save!
|
||||||
DomainBlockWorker.perform_async(@domain_block.id)
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
log_action :create, @domain_block
|
log_action :create, @domain_block
|
||||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||||
|
@ -55,6 +56,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def conflicts_with_existing_block?(domain_block, existing_domain_block)
|
||||||
|
existing_domain_block.present? && (existing_domain_block.domain == TagManager.instance.normalize_domain(domain_block.domain) || !domain_block.stricter_than?(existing_domain_block))
|
||||||
|
end
|
||||||
|
|
||||||
def set_domain_blocks
|
def set_domain_blocks
|
||||||
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,10 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_recently_used_tags
|
def set_recently_used_tags
|
||||||
@recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10)
|
@recently_used_tags = Tag.recently_used(current_account).where.not(id: featured_tag_ids).limit(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
def featured_tag_ids
|
||||||
|
current_account.featured_tags.pluck(:tag_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -198,34 +198,19 @@ module CacheConcern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Rename this method, as it does not perform any caching anymore.
|
||||||
def cache_collection(raw, klass)
|
def cache_collection(raw, klass)
|
||||||
return raw unless klass.respond_to?(:with_includes)
|
return raw unless klass.respond_to?(:preload_cacheable_associations)
|
||||||
|
|
||||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
records = raw.to_a
|
||||||
return [] if raw.empty?
|
|
||||||
|
|
||||||
cached_keys_with_value = begin
|
klass.preload_cacheable_associations(records)
|
||||||
Rails.cache.read_multi(*raw).transform_keys(&:id).transform_values { |r| ActiveRecordCoder.load(r) }
|
|
||||||
rescue ActiveRecordCoder::Error
|
|
||||||
{} # The serialization format may have changed, let's pretend it's a cache miss.
|
|
||||||
end
|
|
||||||
|
|
||||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
records
|
||||||
|
|
||||||
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.index_by(&:id)
|
|
||||||
|
|
||||||
uncached.each_value do |item|
|
|
||||||
Rails.cache.write(item, ActiveRecordCoder.dump(item))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raw.filter_map { |item| cached_keys_with_value[item.id] || uncached[item.id] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Rename this method, as it does not perform any caching anymore.
|
||||||
def cache_collection_paginated_by_id(raw, klass, limit, options)
|
def cache_collection_paginated_by_id(raw, klass, limit, options)
|
||||||
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass
|
cache_collection raw.to_a_paginated_by_id(limit, options), klass
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ module WellKnown
|
||||||
username = username_from_resource
|
username = username_from_resource
|
||||||
|
|
||||||
@account = begin
|
@account = begin
|
||||||
if username == Rails.configuration.x.local_domain
|
if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain
|
||||||
Account.representative
|
Account.representative
|
||||||
else
|
else
|
||||||
Account.find_local!(username)
|
Account.find_local!(username)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class VideoMetadataExtractor
|
||||||
private
|
private
|
||||||
|
|
||||||
def ffmpeg_command_output
|
def ffmpeg_command_output
|
||||||
command = Terrapin::CommandLine.new('ffprobe', '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel')
|
command = Terrapin::CommandLine.new(Rails.configuration.x.ffprobe_binary, '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel')
|
||||||
command.run(path: @path, format: 'json', loglevel: 'fatal')
|
command.run(path: @path, format: 'json', loglevel: 'fatal')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -185,7 +185,7 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock_domain!(other_domain)
|
def unblock_domain!(other_domain)
|
||||||
block = domain_blocks.find_by(domain: other_domain)
|
block = domain_blocks.find_by(domain: normalized_domain(other_domain))
|
||||||
block&.destroy
|
block&.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -313,4 +313,8 @@ module AccountInteractions
|
||||||
def remove_potential_friendship(other_account)
|
def remove_potential_friendship(other_account)
|
||||||
PotentialFriendshipTracker.remove(id, other_account.id)
|
PotentialFriendshipTracker.remove(id, other_account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalized_domain(domain)
|
||||||
|
TagManager.instance.normalize_domain(domain)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,10 @@ module Cacheable
|
||||||
includes(@cache_associated)
|
includes(@cache_associated)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preload_cacheable_associations(records)
|
||||||
|
ActiveRecord::Associations::Preloader.new(records: records, associations: @cache_associated).call
|
||||||
|
end
|
||||||
|
|
||||||
def cache_ids
|
def cache_ids
|
||||||
select(:id, :updated_at)
|
select(:id, :updated_at)
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Feed
|
||||||
unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
|
unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
Status.where(id: unhydrated).cache_ids
|
Status.where(id: unhydrated)
|
||||||
end
|
end
|
||||||
|
|
||||||
def key
|
def key
|
||||||
|
|
|
@ -29,7 +29,7 @@ class PublicFeed
|
||||||
scope.merge!(media_only_scope) if media_only?
|
scope.merge!(media_only_scope) if media_only?
|
||||||
scope.merge!(language_scope) if account&.chosen_languages.present?
|
scope.merge!(language_scope) if account&.chosen_languages.present?
|
||||||
|
|
||||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -338,38 +338,6 @@ class Status < ApplicationRecord
|
||||||
StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
|
StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_stale_associations!(cached_items)
|
|
||||||
account_ids = []
|
|
||||||
|
|
||||||
cached_items.each do |item|
|
|
||||||
account_ids << item.account_id
|
|
||||||
account_ids << item.reblog.account_id if item.reblog?
|
|
||||||
end
|
|
||||||
|
|
||||||
account_ids.uniq!
|
|
||||||
|
|
||||||
status_ids = cached_items.map { |item| item.reblog? ? item.reblog_of_id : item.id }.uniq
|
|
||||||
|
|
||||||
return if account_ids.empty?
|
|
||||||
|
|
||||||
accounts = Account.where(id: account_ids).includes(:account_stat, :user).index_by(&:id)
|
|
||||||
|
|
||||||
status_stats = StatusStat.where(status_id: status_ids).index_by(&:status_id)
|
|
||||||
|
|
||||||
cached_items.each do |item|
|
|
||||||
item.account = accounts[item.account_id]
|
|
||||||
item.reblog.account = accounts[item.reblog.account_id] if item.reblog?
|
|
||||||
|
|
||||||
if item.reblog?
|
|
||||||
status_stat = status_stats[item.reblog.id]
|
|
||||||
item.reblog.status_stat = status_stat if status_stat.present?
|
|
||||||
else
|
|
||||||
status_stat = status_stats[item.id]
|
|
||||||
item.status_stat = status_stat if status_stat.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def from_text(text)
|
def from_text(text)
|
||||||
return [] if text.blank?
|
return [] if text.blank?
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class TagFeed < PublicFeed
|
||||||
scope.merge!(account_filters_scope) if account?
|
scope.merge!(account_filters_scope) if account?
|
||||||
scope.merge!(media_only_scope) if media_only?
|
scope.merge!(media_only_scope) if media_only?
|
||||||
|
|
||||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -96,6 +96,8 @@ class User < ApplicationRecord
|
||||||
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text }
|
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text }
|
||||||
validates :invite_request, presence: true, on: :create, if: :invite_text_required?
|
validates :invite_request, presence: true, on: :create, if: :invite_text_required?
|
||||||
|
|
||||||
|
validates :email, presence: true, email_address: true
|
||||||
|
|
||||||
validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
|
validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
|
||||||
validates_with EmailMxValidator, if: :validate_email_dns?
|
validates_with EmailMxValidator, if: :validate_email_dns?
|
||||||
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::Admin::DomainBlockSerializer < ActiveModel::Serializer
|
class REST::Admin::DomainBlockSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :domain, :created_at, :severity,
|
attributes :id, :domain, :digest, :created_at, :severity,
|
||||||
:reject_media, :reject_reports,
|
:reject_media, :reject_reports,
|
||||||
:private_comment, :public_comment, :obfuscate
|
:private_comment, :public_comment, :obfuscate
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def digest
|
||||||
|
object.domain_digest
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -160,7 +160,7 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
def idempotency_duplicate
|
def idempotency_duplicate
|
||||||
if scheduled?
|
if scheduled?
|
||||||
@account.schedule_statuses.find(@idempotency_duplicate)
|
@account.scheduled_statuses.find(@idempotency_duplicate)
|
||||||
else
|
else
|
||||||
@account.statuses.find(@idempotency_duplicate)
|
@account.statuses.find(@idempotency_duplicate)
|
||||||
end
|
end
|
||||||
|
@ -214,7 +214,7 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def scheduled_options
|
def scheduled_options
|
||||||
@options.tap do |options_hash|
|
@options.dup.tap do |options_hash|
|
||||||
options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id
|
options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id
|
||||||
options_hash[:application_id] = options_hash.delete(:application)&.id
|
options_hash[:application_id] = options_hash.delete(:application)&.id
|
||||||
options_hash[:scheduled_at] = nil
|
options_hash[:scheduled_at] = nil
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# NOTE: I initially wrote this as `EmailValidator` but it ended up clashing
|
||||||
|
# with an indirect dependency of ours, `validate_email`, which, turns out,
|
||||||
|
# has the same approach as we do, but with an extra check disallowing
|
||||||
|
# single-label domains. Decided to not switch to `validate_email` because
|
||||||
|
# we do want to allow at least `localhost`.
|
||||||
|
|
||||||
|
class EmailAddressValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
value = value.strip
|
||||||
|
|
||||||
|
address = Mail::Address.new(value)
|
||||||
|
record.errors.add(attribute, :invalid) if address.address != value
|
||||||
|
rescue Mail::Field::FieldError
|
||||||
|
record.errors.add(attribute, :invalid)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
if ENV['FFMPEG_BINARY'].present?
|
Rails.application.configure do
|
||||||
FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY']
|
config.x.ffmpeg_binary = ENV['FFMPEG_BINARY'] || 'ffmpeg'
|
||||||
|
config.x.ffprobe_binary = ENV['FFPROBE_BINARY'] || 'ffprobe'
|
||||||
end
|
end
|
||||||
|
|
|
@ -224,7 +224,7 @@ module Mastodon::CLI
|
||||||
users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse
|
users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse
|
||||||
ref_user = users.shift
|
ref_user = users.shift
|
||||||
say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow
|
say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow
|
||||||
say "e-mail will be disabled for the following accounts: #{user.map(&:account).map(&:acct).join(', ')}", :yellow
|
say "e-mail will be disabled for the following accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow
|
||||||
say 'Please reach out to them and set another address with `tootctl account modify` or delete them.', :yellow
|
say 'Please reach out to them and set another address with `tootctl account modify` or delete them.', :yellow
|
||||||
|
|
||||||
users.each_with_index do |user, index|
|
users.each_with_index do |user, index|
|
||||||
|
|
|
@ -35,7 +35,7 @@ module Paperclip
|
||||||
dst.binmode
|
dst.binmode
|
||||||
|
|
||||||
begin
|
begin
|
||||||
command = Terrapin::CommandLine.new('ffmpeg', '-i :source -loglevel :loglevel -y :destination', logger: Paperclip.logger)
|
command = Terrapin::CommandLine.new(Rails.configuration.x.ffmpeg_binary, '-i :source -loglevel :loglevel -y :destination', logger: Paperclip.logger)
|
||||||
command.run(source: @file.path, destination: dst.path, loglevel: 'fatal')
|
command.run(source: @file.path, destination: dst.path, loglevel: 'fatal')
|
||||||
rescue Terrapin::ExitStatusError
|
rescue Terrapin::ExitStatusError
|
||||||
dst.close(true)
|
dst.close(true)
|
||||||
|
|
|
@ -61,7 +61,7 @@ module Paperclip
|
||||||
command_arguments, interpolations = prepare_command(destination)
|
command_arguments, interpolations = prepare_command(destination)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
command = Terrapin::CommandLine.new('ffmpeg', command_arguments.join(' '), logger: Paperclip.logger)
|
command = Terrapin::CommandLine.new(Rails.configuration.x.ffmpeg_binary, command_arguments.join(' '), logger: Paperclip.logger)
|
||||||
command.run(interpolations)
|
command.run(interpolations)
|
||||||
rescue Terrapin::ExitStatusError => e
|
rescue Terrapin::ExitStatusError => e
|
||||||
raise Paperclip::Error, "Error while transcoding #{@basename}: #{e}"
|
raise Paperclip::Error, "Error while transcoding #{@basename}: #{e}"
|
||||||
|
|
|
@ -515,6 +515,7 @@ namespace :mastodon do
|
||||||
owner_role = UserRole.find_by(name: 'Owner')
|
owner_role = UserRole.find_by(name: 'Owner')
|
||||||
user = User.new(email: email, password: password, confirmed_at: Time.now.utc, account_attributes: { username: username }, bypass_invite_request_check: true, role: owner_role)
|
user = User.new(email: email, password: password, confirmed_at: Time.now.utc, account_attributes: { username: username }, bypass_invite_request_check: true, role: owner_role)
|
||||||
user.save(validate: false)
|
user.save(validate: false)
|
||||||
|
user.approve!
|
||||||
|
|
||||||
Setting.site_contact_username = username
|
Setting.site_contact_username = username
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,39 @@ describe Api::V1::FeaturedTags::SuggestionsController do
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
|
||||||
let(:account) { Fabricate(:account) }
|
let(:account) { Fabricate(:account, user: user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
it 'returns http success' do
|
let!(:unused_featured_tag) { Fabricate(:tag, name: 'unused_featured_tag') }
|
||||||
|
let!(:used_tag) { Fabricate(:tag, name: 'used_tag') }
|
||||||
|
let!(:used_featured_tag) { Fabricate(:tag, name: 'used_featured_tag') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
_unused_tag = Fabricate(:tag, name: 'unused_tag')
|
||||||
|
|
||||||
|
# Make relevant tags used by account
|
||||||
|
status = Fabricate(:status, account: account)
|
||||||
|
status.tags << used_tag
|
||||||
|
status.tags << used_featured_tag
|
||||||
|
|
||||||
|
# Feature the relevant tags
|
||||||
|
Fabricate :featured_tag, account: account, name: unused_featured_tag.name
|
||||||
|
Fabricate :featured_tag, account: account, name: used_featured_tag.name
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success and recently used but not featured tags', :aggregate_failures do
|
||||||
get :index, params: { account_id: account.id, limit: 2 }
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response)
|
||||||
|
.to have_http_status(200)
|
||||||
|
expect(body_as_json)
|
||||||
|
.to contain_exactly(
|
||||||
|
include(name: used_tag.name)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -221,39 +221,4 @@ describe ApplicationController do
|
||||||
|
|
||||||
include_examples 'respond_with_error', 422
|
include_examples 'respond_with_error', 422
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'cache_collection' do
|
|
||||||
subject do
|
|
||||||
Class.new(ApplicationController) do
|
|
||||||
public :cache_collection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'receives :with_includes' do |fabricator, klass|
|
|
||||||
it 'uses raw if it is not an ActiveRecord::Relation' do
|
|
||||||
record = Fabricate(fabricator)
|
|
||||||
expect(subject.new.cache_collection([record], klass)).to eq [record]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'cacheable' do |fabricator, klass|
|
|
||||||
include_examples 'receives :with_includes', fabricator, klass
|
|
||||||
|
|
||||||
it 'calls cache_ids of raw if it is an ActiveRecord::Relation' do
|
|
||||||
record = Fabricate(fabricator)
|
|
||||||
relation = klass.none
|
|
||||||
allow(relation).to receive(:cache_ids).and_return([record])
|
|
||||||
expect(subject.new.cache_collection(relation, klass)).to eq [record]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns raw unless class responds to :with_includes' do
|
|
||||||
raw = Object.new
|
|
||||||
expect(subject.new.cache_collection(raw, Object)).to eq raw
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with a Status' do
|
|
||||||
include_examples 'cacheable', :status, Status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
Fabricator(:featured_tag) do
|
Fabricator(:featured_tag) do
|
||||||
account { Fabricate.build(:account) }
|
account { Fabricate.build(:account) }
|
||||||
tag { Fabricate.build(:tag) }
|
tag { nil }
|
||||||
name { sequence(:name) { |i| "Tag#{i}" } }
|
name { sequence(:name) { |i| "Tag#{i}" } }
|
||||||
end
|
end
|
||||||
|
|
|
@ -250,6 +250,24 @@ describe AccountInteractions do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#block_idna_domain!' do
|
||||||
|
subject do
|
||||||
|
[
|
||||||
|
account.block_domain!(idna_domain),
|
||||||
|
account.block_domain!(punycode_domain),
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:idna_domain) { '대한민국.한국' }
|
||||||
|
let(:punycode_domain) { 'xn--3e0bs9hfvinn1a.xn--3e0b707e' }
|
||||||
|
|
||||||
|
it 'creates single AccountDomainBlock' do
|
||||||
|
expect do
|
||||||
|
expect(subject).to all(be_a AccountDomainBlock)
|
||||||
|
end.to change { account.domain_blocks.count }.by 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#unfollow!' do
|
describe '#unfollow!' do
|
||||||
subject { account.unfollow!(target_account) }
|
subject { account.unfollow!(target_account) }
|
||||||
|
|
||||||
|
@ -345,6 +363,28 @@ describe AccountInteractions do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#unblock_idna_domain!' do
|
||||||
|
subject { account.unblock_domain!(punycode_domain) }
|
||||||
|
|
||||||
|
let(:idna_domain) { '대한민국.한국' }
|
||||||
|
let(:punycode_domain) { 'xn--3e0bs9hfvinn1a.xn--3e0b707e' }
|
||||||
|
|
||||||
|
context 'when blocking the domain' do
|
||||||
|
it 'returns destroyed AccountDomainBlock' do
|
||||||
|
account_domain_block = Fabricate(:account_domain_block, domain: idna_domain)
|
||||||
|
account.domain_blocks << account_domain_block
|
||||||
|
expect(subject).to be_a AccountDomainBlock
|
||||||
|
expect(subject).to be_destroyed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unblocking idna domain' do
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(subject).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#following?' do
|
describe '#following?' do
|
||||||
subject { account.following?(target_account) }
|
subject { account.following?(target_account) }
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ RSpec.describe HomeFeed do
|
||||||
results = subject.get(3)
|
results = subject.get(3)
|
||||||
|
|
||||||
expect(results.map(&:id)).to eq [3, 2]
|
expect(results.map(&:id)).to eq [3, 2]
|
||||||
expect(results.first.attributes.keys).to eq %w(id updated_at)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,12 @@ RSpec.describe User do
|
||||||
expect(user.valid?).to be true
|
expect(user.valid?).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'is valid with a localhost e-mail address' do
|
||||||
|
user = Fabricate.build(:user, email: 'admin@localhost')
|
||||||
|
user.valid?
|
||||||
|
expect(user.valid?).to be true
|
||||||
|
end
|
||||||
|
|
||||||
it 'cleans out invalid locale' do
|
it 'cleans out invalid locale' do
|
||||||
user = Fabricate.build(:user, locale: 'toto')
|
user = Fabricate.build(:user, locale: 'toto')
|
||||||
expect(user.valid?).to be true
|
expect(user.valid?).to be true
|
||||||
|
|
|
@ -49,6 +49,7 @@ RSpec.describe 'Domain Blocks' do
|
||||||
{
|
{
|
||||||
id: domain_block.id.to_s,
|
id: domain_block.id.to_s,
|
||||||
domain: domain_block.domain,
|
domain: domain_block.domain,
|
||||||
|
digest: domain_block.domain_digest,
|
||||||
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
|
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
|
||||||
severity: domain_block.severity.to_s,
|
severity: domain_block.severity.to_s,
|
||||||
reject_media: domain_block.reject_media,
|
reject_media: domain_block.reject_media,
|
||||||
|
@ -102,6 +103,7 @@ RSpec.describe 'Domain Blocks' do
|
||||||
{
|
{
|
||||||
id: domain_block.id.to_s,
|
id: domain_block.id.to_s,
|
||||||
domain: domain_block.domain,
|
domain: domain_block.domain,
|
||||||
|
digest: domain_block.domain_digest,
|
||||||
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
|
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
|
||||||
severity: domain_block.severity.to_s,
|
severity: domain_block.severity.to_s,
|
||||||
reject_media: domain_block.reject_media,
|
reject_media: domain_block.reject_media,
|
||||||
|
@ -133,14 +135,10 @@ RSpec.describe 'Domain Blocks' do
|
||||||
it_behaves_like 'forbidden for wrong role', ''
|
it_behaves_like 'forbidden for wrong role', ''
|
||||||
it_behaves_like 'forbidden for wrong role', 'Moderator'
|
it_behaves_like 'forbidden for wrong role', 'Moderator'
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'creates a domain block with the expected domain name and severity', :aggregate_failures do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns expected domain name and severity' do
|
|
||||||
subject
|
|
||||||
|
|
||||||
body = body_as_json
|
body = body_as_json
|
||||||
|
|
||||||
|
@ -158,7 +156,44 @@ RSpec.describe 'Domain Blocks' do
|
||||||
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
|
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a stricter domain block already exists' do
|
context 'when a looser domain block already exists on a higher level domain' do
|
||||||
|
let(:params) { { domain: 'foo.bar.com', severity: :suspend } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Fabricate(:domain_block, domain: 'bar.com', severity: :silence)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a domain block with the expected domain name and severity', :aggregate_failures do
|
||||||
|
subject
|
||||||
|
|
||||||
|
body = body_as_json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(body).to match a_hash_including(
|
||||||
|
{
|
||||||
|
domain: 'foo.bar.com',
|
||||||
|
severity: 'suspend',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a domain block already exists on the same domain' do
|
||||||
|
before do
|
||||||
|
Fabricate(:domain_block, domain: 'foo.bar.com', severity: :silence)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns existing domain block in error', :aggregate_failures do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
expect(body_as_json[:existing_domain_block][:domain]).to eq('foo.bar.com')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a stricter domain block already exists on a higher level domain' do
|
||||||
before do
|
before do
|
||||||
Fabricate(:domain_block, domain: 'bar.com', severity: :suspend)
|
Fabricate(:domain_block, domain: 'bar.com', severity: :suspend)
|
||||||
end
|
end
|
||||||
|
@ -212,6 +247,7 @@ RSpec.describe 'Domain Blocks' do
|
||||||
{
|
{
|
||||||
id: domain_block.id.to_s,
|
id: domain_block.id.to_s,
|
||||||
domain: domain_block.domain,
|
domain: domain_block.domain,
|
||||||
|
digest: domain_block.domain_digest,
|
||||||
severity: 'suspend',
|
severity: 'suspend',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,6 +54,13 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
it 'does not change statuses count' do
|
it 'does not change statuses count' do
|
||||||
expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not(change { [account.statuses_count, previous_status.replies_count] })
|
expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not(change { [account.statuses_count, previous_status.replies_count] })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns existing status when used twice with idempotency key' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||||
|
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||||
|
expect(status2.id).to eq status1.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates response to the original status of boost' do
|
it 'creates response to the original status of boost' do
|
||||||
|
|
Loading…
Reference in New Issue