diff options
46 files changed, 235 insertions, 80 deletions
diff --git a/Gemfile b/Gemfile index 6421693da..760ecbc7c 100644 --- a/Gemfile +++ b/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' diff --git a/Gemfile.lock b/Gemfile.lock index ed35f4a7b..4c03ffc5e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 3d20f0e88..85d9e784a 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -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 diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb new file mode 100644 index 000000000..0f04b488f --- /dev/null +++ b/app/controllers/api/v1/endorsements_controller.rb @@ -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 diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index c6925d462..49a52f7a6 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cc92894a5..27cd0f4f9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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' diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb index c9725ccc0..5d306e600 100644 --- a/app/controllers/emojis_controller.rb +++ b/app/controllers/emojis_controller.rb @@ -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 diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 1940aaa1b..bc711a8e4 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -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 diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 9597fe89d..7f1ff8e3b 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -72,7 +72,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]); } diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js index b3462e25a..8cfbac1bb 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js @@ -168,6 +168,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { const computedClass = classNames('composer--options--dropdown', { active, open, + top: placement === 'top', }); // The result. diff --git a/app/javascript/flavours/glitch/features/ui/components/column_link.js b/app/javascript/flavours/glitch/features/ui/components/column_link.js index b058aa963..1b6d7d09e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/column_link.js +++ b/app/javascript/flavours/glitch/features/ui/components/column_link.js @@ -22,8 +22,13 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => { </Link> ); } else { + const handleOnClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + return onClick(e); + } return ( - <a onClick={onClick} className='column-link' role='button' tabIndex='0' data-method={method}> + <a href='#' onClick={onClick && handleOnClick} className='column-link' tabIndex='0'> <i className={`fa fa-fw fa-${icon} column-link__icon`} /> {text} {badgeElement} diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 7024e60da..d58e11b55 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -92,6 +92,7 @@ const keyMap = { goToProfile: 'g u', goToBlocked: 'g b', goToMuted: 'g m', + goToRequests: 'g r', toggleSpoiler: 'x', }; @@ -369,6 +370,10 @@ export default class UI extends React.Component { this.props.history.push('/mutes'); } + handleHotkeyGoToRequests = () => { + this.props.history.push('/follow_requests'); + } + render () { const { width, draggingOver } = this.state; const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen } = this.props; @@ -408,6 +413,7 @@ export default class UI extends React.Component { goToProfile: this.handleHotkeyGoToProfile, goToBlocked: this.handleHotkeyGoToBlocked, goToMuted: this.handleHotkeyGoToMuted, + goToRequests: this.handleHotkeyGoToRequests, }; return ( diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss index fab94d8c3..2267b798c 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/composer.scss @@ -406,6 +406,12 @@ background: $ui-highlight-color; transition: none; } + &.top { + & > .value { + border-radius: 0 0 4px 4px; + box-shadow: 0 4px 4px rgba($base-shadow-color, 0.1); + } + } } } diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 11a199db6..e8fd441e1 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -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]); } diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 0a6e7c627..e83f724e9 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -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> diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index a772c1c95..e19778fd2 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -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> diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 67484fc63..34d52a7d2 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -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 ( diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 106198f74..70f08a8eb 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -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 => { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 64a00c2c3..6073f9c0e 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -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; } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 375c7b64b..22dbfa8cf 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -154,9 +154,8 @@ code { margin-bottom: 15px; } - li { - float: left; - width: 50%; + ul { + columns: 2; } } diff --git a/app/models/account_pin.rb b/app/models/account_pin.rb index 9a21c3405..b51d3d4cd 100644 --- a/app/models/account_pin.rb +++ b/app/models/account_pin.rb @@ -11,6 +11,7 @@ # class AccountPin < ApplicationRecord + include Paginable include RelationshipCacheable belongs_to :account diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb index 4f08a3049..8f5fd1fa2 100644 --- a/app/models/form/status_batch.rb +++ b/app/models/form/status_batch.rb @@ -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 diff --git a/app/models/notification.rb b/app/models/notification.rb index 4f6ec8e8e..b9bec0808 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -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 diff --git a/app/models/status.rb b/app/models/status.rb index 8cd6d3862..f38227b85 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -26,8 +26,6 @@ # class Status < ApplicationRecord - self.cache_versioning = false - include Paginable include Streamable include Cacheable diff --git a/app/services/after_block_domain_from_account_service.rb b/app/services/after_block_domain_from_account_service.rb index 0f1a8505d..56cc819fb 100644 --- a/app/services/after_block_domain_from_account_service.rb +++ b/app/services/after_block_domain_from_account_service.rb @@ -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 diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 8492c1117..da7db6462 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -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 diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb index d082de40b..a1fe93665 100644 --- a/app/services/block_domain_service.rb +++ b/app/services/block_domain_service.rb @@ -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? diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index fb889140b..1a53093b8 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -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 diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 0a98f5fb9..8fc79b8ad 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -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 diff --git a/app/workers/maintenance/uncache_media_worker.rb b/app/workers/maintenance/uncache_media_worker.rb index f6a51a1b8..2d1a670a7 100644 --- a/app/workers/maintenance/uncache_media_worker.rb +++ b/app/workers/maintenance/uncache_media_worker.rb @@ -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 diff --git a/app/workers/refollow_worker.rb b/app/workers/refollow_worker.rb index 66bcd27c3..12f2bf671 100644 --- a/app/workers/refollow_worker.rb +++ b/app/workers/refollow_worker.rb @@ -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) diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb index 5ab16c057..023a77307 100644 --- a/app/workers/scheduler/backup_cleanup_scheduler.rb +++ b/app/workers/scheduler/backup_cleanup_scheduler.rb @@ -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 diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb index bab4ae886..fec08c6bc 100644 --- a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb +++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb @@ -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 diff --git a/app/workers/scheduler/email_scheduler.rb b/app/workers/scheduler/email_scheduler.rb index 36866061b..24117e424 100644 --- a/app/workers/scheduler/email_scheduler.rb +++ b/app/workers/scheduler/email_scheduler.rb @@ -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 diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb index 42cf14128..b02bac883 100644 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ b/app/workers/scheduler/feed_cleanup_scheduler.rb @@ -3,6 +3,8 @@ class Scheduler::FeedCleanupScheduler include Sidekiq::Worker + sidekiq_options unique: :until_executed + def perform clean_home_feeds! clean_list_feeds! diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb index 613a5e336..6bb93df7d 100644 --- a/app/workers/scheduler/ip_cleanup_scheduler.rb +++ b/app/workers/scheduler/ip_cleanup_scheduler.rb @@ -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 diff --git a/app/workers/scheduler/media_cleanup_scheduler.rb b/app/workers/scheduler/media_cleanup_scheduler.rb index c35686fcb..a27e02953 100644 --- a/app/workers/scheduler/media_cleanup_scheduler.rb +++ b/app/workers/scheduler/media_cleanup_scheduler.rb @@ -3,6 +3,8 @@ class Scheduler::MediaCleanupScheduler include Sidekiq::Worker + sidekiq_options unique: :until_executed + def perform unattached_media.find_each(&:destroy) end diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb index af2ae3120..06ba66205 100644 --- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb +++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb @@ -3,6 +3,8 @@ class Scheduler::SubscriptionsCleanupScheduler include Sidekiq::Worker + sidekiq_options unique: :until_executed + def perform Subscription.expired.in_batches.delete_all end diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb index dc16e85c2..4b0959af2 100644 --- a/app/workers/scheduler/subscriptions_scheduler.rb +++ b/app/workers/scheduler/subscriptions_scheduler.rb @@ -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 diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb index 245536cea..626fb1652 100644 --- a/app/workers/scheduler/user_cleanup_scheduler.rb +++ b/app/workers/scheduler/user_cleanup_scheduler.rb @@ -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 diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index c134bc5b8..59ab9b9a1 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -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 diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 7fa2bc8de..96339e35e 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -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 diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 5d65e1ffb..924173140 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 3e0be9380..dffa2fb8b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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' diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index f693c8b5a..191ce634c 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -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 diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb index fd303a9d5..8a5bd3301 100644 --- a/spec/services/suspend_account_service_spec.rb +++ b/spec/services/suspend_account_service_spec.rb @@ -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 |