commit
37965ac8d9
3
Gemfile
3
Gemfile
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,7 +3315,14 @@ a.status-card {
|
|||
border-radius: 4px;
|
||||
margin-left: 40px;
|
||||
overflow: hidden;
|
||||
transform-origin: 50% 0;
|
||||
|
||||
&.top {
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
transform-origin: 50% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-dropdown__option {
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -154,9 +154,8 @@ code {
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
width: 50%;
|
||||
ul {
|
||||
columns: 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#
|
||||
|
||||
class AccountPin < ApplicationRecord
|
||||
include Paginable
|
||||
include RelationshipCacheable
|
||||
|
||||
belongs_to :account
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -26,8 +26,6 @@
|
|||
#
|
||||
|
||||
class Status < ApplicationRecord
|
||||
self.cache_versioning = false
|
||||
|
||||
include Paginable
|
||||
include Streamable
|
||||
include Cacheable
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class Scheduler::FeedCleanupScheduler
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options unique: :until_executed
|
||||
|
||||
def perform
|
||||
clean_home_feeds!
|
||||
clean_list_feeds!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class Scheduler::MediaCleanupScheduler
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options unique: :until_executed
|
||||
|
||||
def perform
|
||||
unattached_media.find_each(&:destroy)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class Scheduler::SubscriptionsCleanupScheduler
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options unique: :until_executed
|
||||
|
||||
def perform
|
||||
Subscription.expired.in_batches.delete_all
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -267,18 +267,19 @@ Rails.application.routes.draw do
|
|||
|
||||
get '/search', to: 'search#index', as: :search
|
||||
|
||||
resources :follows, only: [:create]
|
||||
resources :media, only: [:create, :update]
|
||||
resources :blocks, only: [:index]
|
||||
resources :mutes, only: [:index] do
|
||||
resources :follows, only: [:create]
|
||||
resources :media, only: [:create, :update]
|
||||
resources :blocks, only: [:index]
|
||||
resources :mutes, only: [:index] do
|
||||
collection do
|
||||
get 'details'
|
||||
end
|
||||
end
|
||||
resources :favourites, only: [:index]
|
||||
resources :bookmarks, only: [:index]
|
||||
resources :reports, only: [:index, :create]
|
||||
resources :filters, only: [:index, :create, :show, :update, :destroy]
|
||||
resources :favourites, only: [:index]
|
||||
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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue