Merge pull request #660 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
pull/661/head
ThibG 2018-08-21 19:54:21 +02:00 committed by GitHub
commit 37965ac8d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 215 additions and 78 deletions

View File

@ -16,7 +16,6 @@ gem 'dotenv-rails', '~> 2.2', '< 2.3'
gem 'aws-sdk-s3', '~> 1.9', require: false
gem 'fog-core', '~> 1.45'
gem 'fog-local', '~> 0.5', require: false
gem 'fog-openstack', '~> 0.1', require: false
gem 'paperclip', '~> 6.0'
gem 'paperclip-av-transcoder', '~> 0.6'
@ -42,7 +41,7 @@ gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.2'
gem 'doorkeeper', '~> 4.2', '< 4.3'
gem 'doorkeeper', '~> 4.4'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1'

View File

@ -181,7 +181,7 @@ GEM
docile (1.3.0)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6)
doorkeeper (4.4.2)
railties (>= 4.2)
dotenv (2.2.2)
dotenv-rails (2.2.2)
@ -220,8 +220,6 @@ GEM
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
fog-local (0.5.0)
fog-core (>= 1.27, < 3.0)
fog-openstack (0.1.25)
fog-core (~> 1.40)
fog-json (>= 1.0)
@ -674,14 +672,13 @@ DEPENDENCIES
devise (~> 4.4)
devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 9.1)
doorkeeper (~> 4.2, < 4.3)
doorkeeper (~> 4.4)
dotenv-rails (~> 2.2, < 2.3)
fabrication (~> 2.20)
faker (~> 1.8)
fast_blank (~> 1.0)
fastimage
fog-core (~> 1.45)
fog-local (~> 0.5)
fog-openstack (~> 0.1)
fuubar (~> 2.2)
goldfinger (~> 2.1)

View File

@ -43,7 +43,7 @@ class AccountsController < ApplicationController
format.json do
skip_session!
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
end
end

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
class Api::V1::EndorsementsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
def load_accounts
if unlimited?
endorsed_accounts.all
else
endorsed_accounts.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
end
def endorsed_accounts
current_account.endorsed_accounts
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
return if unlimited?
if records_continue?
api_v1_endorsements_url pagination_params(max_id: pagination_max_id)
end
end
def prev_path
return if unlimited?
unless @accounts.empty?
api_v1_endorsements_url pagination_params(since_id: pagination_since_id)
end
end
def pagination_max_id
@accounts.last.id
end
def pagination_since_id
@accounts.first.id
end
def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def unlimited?
params[:limit] == '0'
end
end

View File

@ -17,8 +17,7 @@ class Api::V1::StatusesController < Api::BaseController
CONTEXT_LIMIT = 4_096
def show
cached = Rails.cache.read(@status.cache_key)
@status = cached unless cached.nil?
@status = cache_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer
end

View File

@ -178,12 +178,8 @@ class ApplicationController < ActionController::Base
return raw unless klass.respond_to?(:with_includes)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
uncached_ids = []
cached_keys_with_value = Rails.cache.read_multi(*raw.map(&:cache_key))
raw.each do |item|
uncached_ids << item.id unless cached_keys_with_value.key?(item.cache_key)
end
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!)
@ -191,11 +187,11 @@ class ApplicationController < ActionController::Base
uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
uncached.each_value do |item|
Rails.cache.write(item.cache_key, item)
Rails.cache.write(item, item)
end
end
raw.map { |item| cached_keys_with_value[item.cache_key] || uncached[item.id] }.compact
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
end
def respond_with_error(code)
@ -211,7 +207,6 @@ class ApplicationController < ActionController::Base
def render_cached_json(cache_key, **options)
options[:expires_in] ||= 3.minutes
cache_key = cache_key.join(':') if cache_key.is_a?(Enumerable)
cache_public = options.key?(:public) ? options.delete(:public) : true
content_type = options.delete(:content_type) || 'application/json'

View File

@ -9,7 +9,7 @@ class EmojisController < ApplicationController
format.json do
skip_session!
render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
end
end

View File

@ -34,7 +34,7 @@ class StatusesController < ApplicationController
format.json do
skip_session! unless @stream_entry.hidden?
render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
end
end
@ -44,7 +44,7 @@ class StatusesController < ApplicationController
def activity
skip_session!
render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
end
end

View File

@ -55,7 +55,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
return;
}
if (!params.max_id && timeline.get('items', ImmutableList()).size > 0) {
if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
params.since_id = timeline.getIn(['items', 0]);
}

View File

@ -137,7 +137,7 @@ class DropdownMenu extends React.PureComponent {
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<ul>

View File

@ -28,6 +28,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
style: PropTypes.object,
items: PropTypes.array.isRequired,
value: PropTypes.string.isRequired,
placement: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
@ -119,7 +120,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
render () {
const { mounted } = this.state;
const { style, items, value } = this.props;
const { style, items, placement, value } = this.props;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
@ -127,7 +128,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
{items.map(item => (
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div className='privacy-dropdown__option__icon'>
@ -226,7 +227,7 @@ export default class PrivacyDropdown extends React.PureComponent {
const valueOption = this.options.find(item => item.value === value);
return (
<div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}>
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}>
<IconButton
className='privacy-dropdown__value-icon'
@ -247,6 +248,7 @@ export default class PrivacyDropdown extends React.PureComponent {
value={value}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
/>
</Overlay>
</div>

View File

@ -89,6 +89,7 @@ const keyMap = {
goToProfile: 'g u',
goToBlocked: 'g b',
goToMuted: 'g m',
goToRequests: 'g r',
toggleHidden: 'x',
};
@ -427,6 +428,10 @@ export default class UI extends React.PureComponent {
this.context.router.history.push('/mutes');
}
handleHotkeyGoToRequests = () => {
this.context.router.history.push('/follow_requests');
}
render () {
const { draggingOver } = this.state;
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
@ -449,6 +454,7 @@ export default class UI extends React.PureComponent {
goToProfile: this.handleHotkeyGoToProfile,
goToBlocked: this.handleHotkeyGoToBlocked,
goToMuted: this.handleHotkeyGoToMuted,
goToRequests: this.handleHotkeyGoToRequests,
};
return (

View File

@ -1,5 +1,6 @@
import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { me } from '../initial_state';
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
@ -83,7 +84,7 @@ export const makeGetStatus = () => {
statusReblog = null;
}
const regex = regexFromFilters(filters);
const regex = (accountReblog || accountBase).get('id') !== me && regexFromFilters(filters);
const filtered = regex && regex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'));
return statusBase.withMutations(map => {

View File

@ -230,7 +230,6 @@
.dropdown-menu {
position: absolute;
transform-origin: 50% 0;
}
.invisible {
@ -1634,6 +1633,22 @@ a.account__display-name {
ul {
list-style: none;
}
&.left {
transform-origin: 100% 50%;
}
&.top {
transform-origin: 50% 100%;
}
&.bottom {
transform-origin: 50% 0;
}
&.right {
transform-origin: 0 50%;
}
}
.dropdown-menu__arrow {
@ -3300,8 +3315,15 @@ a.status-card {
border-radius: 4px;
margin-left: 40px;
overflow: hidden;
&.top {
transform-origin: 50% 100%;
}
&.bottom {
transform-origin: 50% 0;
}
}
.privacy-dropdown__option {
color: $inverted-text-color;
@ -3372,6 +3394,10 @@ a.status-card {
}
}
&.top .privacy-dropdown__value {
border-radius: 0 0 4px 4px;
}
.privacy-dropdown__dropdown {
display: block;
box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1);
@ -4176,6 +4202,10 @@ a.status-card {
color: $highlight-text-color;
}
.status__content p {
color: $inverted-text-color;
}
@media screen and (max-width: 480px) {
max-height: 10vh;
}

View File

@ -154,9 +154,8 @@ code {
margin-bottom: 15px;
}
li {
float: left;
width: 50%;
ul {
columns: 2;
}
}

View File

@ -11,6 +11,7 @@
#
class AccountPin < ApplicationRecord
include Paginable
include RelationshipCacheable
belongs_to :account

View File

@ -23,7 +23,7 @@ class Form::StatusBatch
media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id)
ApplicationRecord.transaction do
Status.where(id: media_attached_status_ids).find_each do |status|
Status.where(id: media_attached_status_ids).reorder(nil).find_each do |status|
status.update!(sensitive: sensitive)
log_action :update, status
end
@ -35,7 +35,7 @@ class Form::StatusBatch
end
def delete_statuses
Status.where(id: status_ids).find_each do |status|
Status.where(id: status_ids).reorder(nil).find_each do |status|
RemovalWorker.perform_async(status.id)
log_action :destroy, status
end

View File

@ -39,8 +39,6 @@ class Notification < ApplicationRecord
validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }
scope :cache_ids, -> { select(:id, :updated_at, :activity_type, :activity_id) }
scope :browserable, ->(exclude_types = []) {
types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types + [:follow_request])
where(activity_type: types)
@ -68,6 +66,10 @@ class Notification < ApplicationRecord
end
class << self
def cache_ids
select(:id, :updated_at, :activity_type, :activity_id)
end
def reload_stale_associations!(cached_items)
account_ids = (cached_items.map(&:from_account_id) + cached_items.map { |item| item.target_status&.account_id }.compact).uniq

View File

@ -26,8 +26,6 @@
#
class Status < ApplicationRecord
self.cache_versioning = false
include Paginable
include Streamable
include Cacheable

View File

@ -15,13 +15,13 @@ class AfterBlockDomainFromAccountService < BaseService
private
def reject_existing_followers!
@account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow|
@account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow|
reject_follow!(follow)
end
end
def reject_pending_follow_requests!
FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow_request|
FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow_request|
reject_follow!(follow_request)
end
end

View File

@ -18,7 +18,7 @@ class BackupService < BaseService
def build_json!
@collection = serialize(collection_presenter, ActivityPub::CollectionSerializer)
account.statuses.with_includes.find_in_batches do |statuses|
account.statuses.with_includes.reorder(nil).find_in_batches do |statuses|
statuses.each do |status|
item = serialize(status, ActivityPub::ActivitySerializer)
item.delete(:'@context')
@ -60,7 +60,7 @@ class BackupService < BaseService
end
def dump_media_attachments!(tar)
MediaAttachment.attached.where(account: account).find_in_batches do |media_attachments|
MediaAttachment.attached.where(account: account).reorder(nil).find_in_batches do |media_attachments|
media_attachments.each do |m|
download_to_tar(tar, m.file, m.file.path)
end

View File

@ -43,14 +43,14 @@ class BlockDomainService < BaseService
end
def suspend_accounts!
blocked_domain_accounts.where(suspended: false).find_each do |account|
blocked_domain_accounts.where(suspended: false).reorder(nil).find_each do |account|
UnsubscribeService.new.call(account) if account.subscribed?
SuspendAccountService.new.call(account)
end
end
def clear_account_images!
blocked_domain_accounts.find_each do |account|
blocked_domain_accounts.reorder(nil).find_each do |account|
account.avatar.destroy if account.avatar.exists?
account.header.destroy if account.header.exists?
account.save
@ -58,7 +58,7 @@ class BlockDomainService < BaseService
end
def clear_account_attachments!
media_from_blocked_domain.find_each do |attachment|
media_from_blocked_domain.reorder(nil).find_each do |attachment|
@affected_status_ids << attachment.status_id if attachment.status_id.present?
attachment.file.destroy if attachment.file.exists?

View File

@ -43,13 +43,13 @@ class RemoveStatusService < BaseService
end
def remove_from_followers
@account.followers_for_local_distribution.find_each do |follower|
@account.followers_for_local_distribution.reorder(nil).find_each do |follower|
FeedManager.instance.unpush_from_home(follower, @status)
end
end
def remove_from_lists
@account.lists_for_local_distribution.select(:id, :account_id).find_each do |list|
@account.lists_for_local_distribution.select(:id, :account_id).reorder(nil).find_each do |list|
FeedManager.instance.unpush_from_list(list, @status)
end
end

View File

@ -23,9 +23,7 @@ class SuspendAccountService < BaseService
def purge_content!
if @account.local?
ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id)
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
[delete_actor_json, @account.id, inbox_url]
end
end
@ -75,4 +73,8 @@ class SuspendAccountService < BaseService
@delete_actor_json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
end
def delivery_inboxes
Account.inboxes + Relay.enabled.pluck(:inbox_url)
end
end

View File

@ -8,7 +8,7 @@ class Maintenance::UncacheMediaWorker
def perform(media_attachment_id)
media = MediaAttachment.find(media_attachment_id)
return unless media.file.exists?
return if media.file.blank?
media.file.destroy
media.save

View File

@ -9,7 +9,7 @@ class RefollowWorker
target_account = Account.find(target_account_id)
return unless target_account.protocol == :activitypub
target_account.followers.where(domain: nil).find_each do |follower|
target_account.followers.where(domain: nil).reorder(nil).find_each do |follower|
# Locally unfollow remote account
follower.unfollow!(target_account)

View File

@ -3,8 +3,10 @@
class Scheduler::BackupCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
old_backups.find_each(&:destroy!)
old_backups.reorder(nil).find_each(&:destroy!)
end
private

View File

@ -3,6 +3,8 @@
class Scheduler::DoorkeeperCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all

View File

@ -3,8 +3,10 @@
class Scheduler::EmailScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
eligible_users.find_each do |user|
eligible_users.reorder(nil).find_each do |user|
next unless user.allows_digest_emails?
DigestMailerWorker.perform_async(user.id)
end

View File

@ -3,6 +3,8 @@
class Scheduler::FeedCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
clean_home_feeds!
clean_list_feeds!

View File

@ -5,6 +5,8 @@ class Scheduler::IpCleanupScheduler
RETENTION_PERIOD = 1.year
sidekiq_options unique: :until_executed
def perform
time_ago = RETENTION_PERIOD.ago
SessionActivation.where('updated_at < ?', time_ago).destroy_all

View File

@ -3,6 +3,8 @@
class Scheduler::MediaCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
unattached_media.find_each(&:destroy)
end

View File

@ -3,6 +3,8 @@
class Scheduler::SubscriptionsCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
Subscription.expired.in_batches.delete_all
end

View File

@ -3,6 +3,8 @@
class Scheduler::SubscriptionsScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
Pubsubhubbub::SubscribeWorker.push_bulk(expiring_accounts.pluck(:id))
end

View File

@ -3,8 +3,10 @@
class Scheduler::UserCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).find_in_batches do |batch|
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|
Account.where(id: batch.map(&:account_id)).delete_all
User.where(id: batch.map(&:id)).delete_all
end

View File

@ -74,14 +74,10 @@ elsif ENV['SWIFT_ENABLED'] == 'true'
fog_public: true
)
else
require 'fog/local'
Paperclip::Attachment.default_options.merge!(
fog_credentials: {
provider: 'Local',
local_root: ENV.fetch('PAPERCLIP_ROOT_PATH') { Rails.root.join('public', 'system') },
},
fog_directory: '',
fog_host: ENV.fetch('PAPERCLIP_ROOT_URL') { '/system' }
storage: :filesystem,
use_timestamp: true,
path: (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
url: (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename',
)
end

View File

@ -725,7 +725,7 @@ fi:
tip_local_timeline: Paikallinen aikajana näyttää instanssin %{instance} käyttäjien julkaisut. He ovat naapureitasi!
tip_mobile_webapp: Jos voit lisätä Mastodonin mobiiliselaimen kautta aloitusnäytöllesi, voit vastaanottaa push-ilmoituksia. Toiminta vastaa monin tavoin tavanomaista sovellusta!
tips: Vinkkejä
title: Tervetuloa mukaan, %name}!
title: Tervetuloa mukaan, %{name}!
users:
invalid_email: Virheellinen sähköpostiosoite
invalid_otp_token: Virheellinen kaksivaiheisen todentamisen koodi

View File

@ -6,6 +6,7 @@ pl:
about_this: O tej instancji
administered_by: 'Administrowana przez:'
api: API
apps: Aplikacje
closed_registrations: Rejestracja na tej instancji jest obecnie zamknięta. Możesz jednak zarejestrować się na innej instancji, uzyskując dostęp do tej samej sieci.
contact: Kontakt
contact_missing: Nie ustawiono
@ -281,6 +282,7 @@ pl:
search: Szukaj
title: Znane instancje
invites:
deactivate_all: Unieważnij wszystkie
filter:
all: Wszystkie
available: Dostępne
@ -663,11 +665,14 @@ pl:
publishing: Publikowanie
web: Sieć
remote_follow:
acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić
acct: Podaj swój adres (nazwa@domena), z którego chcesz wykonać działanie
missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
no_account_html: Nie masz konta? Możesz <a href='%{sign_up_path}' target='_blank'>zarejestrować się tutaj</a>
proceed: Śledź
prompt: 'Zamierzasz śledzić:'
remote_interaction:
proceed: Przejdź do interakcji
prompt: 'Chcesz dokonać interakcji z tym wpisem:'
remote_unfollow:
error: Błąd
title: Tytuł
@ -756,6 +761,7 @@ pl:
private: Nie możesz przypiąć niepublicznego wpisu
reblog: Nie możesz przypiąć podbicia wpisu
show_more: Pokaż więcej
sign_in_to_participate: Zaloguj się, aby udzielić się w tej konwersacji
title: '%{name}: "%{quote}"'
visibilities:
private: Tylko dla śledzących

View File

@ -279,6 +279,7 @@ Rails.application.routes.draw do
resources :bookmarks, only: [:index]
resources :reports, only: [:index, :create]
resources :filters, only: [:index, :create, :show, :update, :destroy]
resources :endorsements, only: [:index]
namespace :apps do
get :verify_credentials, to: 'credentials#show'

View File

@ -503,7 +503,7 @@ namespace :mastodon do
desc 'Remove media attachments attributed to silenced accounts'
task remove_silenced: :environment do
nb_media_attachments = 0
MediaAttachment.where(account: Account.silenced).select(:id).find_in_batches do |media_attachments|
MediaAttachment.where(account: Account.silenced).select(:id).reorder(nil).find_in_batches do |media_attachments|
nb_media_attachments += media_attachments.length
Maintenance::DestroyMediaWorker.push_bulk(media_attachments.map(&:id))
end
@ -515,7 +515,7 @@ namespace :mastodon do
time_ago = ENV.fetch('NUM_DAYS') { 7 }.to_i.days.ago
nb_media_attachments = 0
MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).find_in_batches do |media_attachments|
MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).reorder(nil).find_in_batches do |media_attachments|
nb_media_attachments += media_attachments.length
Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id))
end
@ -535,7 +535,7 @@ namespace :mastodon do
accounts = accounts.where(domain: ENV['DOMAIN']) if ENV['DOMAIN'].present?
nb_accounts = 0
accounts.select(:id).find_in_batches do |accounts_batch|
accounts.select(:id).reorder(nil).find_in_batches do |accounts_batch|
nb_accounts += accounts_batch.length
Maintenance::RedownloadAccountMediaWorker.push_bulk(accounts_batch.map(&:id))
end
@ -570,7 +570,7 @@ namespace :mastodon do
desc 'Generates home timelines for users who logged in in the past two weeks'
task build: :environment do
User.active.select(:id, :account_id).find_in_batches do |users|
User.active.select(:id, :account_id).reorder(nil).find_in_batches do |users|
RegenerationWorker.push_bulk(users.map(&:account_id))
end
end

View File

@ -2,6 +2,11 @@ require 'rails_helper'
RSpec.describe SuspendAccountService, type: :service do
describe '#call' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
subject do
-> { described_class.new.call(account) }
end
@ -14,6 +19,8 @@ RSpec.describe SuspendAccountService, type: :service do
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) }
it 'deletes associated records' do
is_expected.to change {
@ -29,5 +36,11 @@ RSpec.describe SuspendAccountService, type: :service do
].map(&:count)
}.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
end
it 'sends a delete actor activity to all known inboxes' do
subject.call
expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once
expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once
end
end
end