diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 43af4f670..dbd5beac2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -26,7 +26,7 @@ Lint/NonLocalExitFromIterator: # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 144 + Max: 125 # Configuration parameters: CountBlocks, Max. Metrics/BlockNesting: diff --git a/Dockerfile b/Dockerfile index 80ca4ec80..cc370d8a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -247,7 +247,9 @@ RUN \ RUN \ # Pre-create and chown system volume to Mastodon user mkdir -p /opt/mastodon/public/system; \ - chown mastodon:mastodon /opt/mastodon/public/system; + chown mastodon:mastodon /opt/mastodon/public/system; \ +# Set Mastodon user as owner of tmp folder + chown -R mastodon:mastodon /opt/mastodon/tmp; # Set the running user for resulting container USER mastodon diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 850bf881f..4e475fe78 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -50,7 +50,7 @@ class AccountsController < ApplicationController end def only_media_scope - Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) + Status.joins(:media_attachments).merge(@account.media_attachments).group(:id) end def no_replies_scope diff --git a/app/controllers/admin/export_domain_allows_controller.rb b/app/controllers/admin/export_domain_allows_controller.rb index adfc39da2..ca88c6525 100644 --- a/app/controllers/admin/export_domain_allows_controller.rb +++ b/app/controllers/admin/export_domain_allows_controller.rb @@ -4,7 +4,7 @@ require 'csv' module Admin class ExportDomainAllowsController < BaseController - include AdminExportControllerConcern + include Admin::ExportControllerConcern before_action :set_dummy_import!, only: [:new] diff --git a/app/controllers/admin/export_domain_blocks_controller.rb b/app/controllers/admin/export_domain_blocks_controller.rb index 816422d4f..433b8a158 100644 --- a/app/controllers/admin/export_domain_blocks_controller.rb +++ b/app/controllers/admin/export_domain_blocks_controller.rb @@ -4,7 +4,7 @@ require 'csv' module Admin class ExportDomainBlocksController < BaseController - include AdminExportControllerConcern + include Admin::ExportControllerConcern before_action :set_dummy_import!, only: [:new] diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 135c57565..c81ba32b0 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -4,9 +4,9 @@ class Api::BaseController < ApplicationController DEFAULT_STATUSES_LIMIT = 20 DEFAULT_ACCOUNTS_LIMIT = 40 - include RateLimitHeaders - include AccessTokenTrackingConcern - include ApiCachingConcern + include Api::RateLimitHeaders + include Api::AccessTokenTrackingConcern + include Api::CachingConcern include Api::ContentSecurityPolicy skip_before_action :require_functional!, unless: :limited_federation_mode? @@ -105,7 +105,7 @@ class Api::BaseController < ApplicationController end def require_not_suspended! - render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.suspended? + render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.unavailable? end def require_user! diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 1a996d362..21b1095f1 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -26,7 +26,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController end def hide_results? - @account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) + @account.unavailable? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 6e6ebae43..1db521f79 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -26,7 +26,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController end def hide_results? - @account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) + @account.unavailable? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 51f541bd2..fe4279302 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -19,7 +19,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def load_statuses - @account.suspended? ? [] : cached_account_statuses + @account.unavailable? ? [] : cached_account_statuses end def cached_account_statuses diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index 35be54930..4339bee21 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -8,6 +8,11 @@ class Api::V2::SearchController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:search' } before_action :validate_search_params! + with_options unless: :user_signed_in? do + before_action :query_pagination_error, if: :pagination_requested? + before_action :remote_resolve_error, if: :remote_resolve_requested? + end + def index @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer @@ -21,12 +26,22 @@ class Api::V2::SearchController < Api::BaseController def validate_search_params! params.require(:q) + end - return if user_signed_in? + def query_pagination_error + render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401 + end - return render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401 if params[:offset].present? + def remote_resolve_error + render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401 + end - render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401 if truthy_param?(:resolve) + def remote_resolve_requested? + truthy_param?(:resolve) + end + + def pagination_requested? + params[:offset].present? end def search_results @@ -34,7 +49,15 @@ class Api::V2::SearchController < Api::BaseController params[:q], current_account, limit_param(RESULTS_LIMIT), - search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed), following: truthy_param?(:following)) + combined_search_params + ) + end + + def combined_search_params + search_params.merge( + resolve: truthy_param?(:resolve), + exclude_unreviewed: truthy_param?(:exclude_unreviewed), + following: truthy_param?(:following) ) end diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 05e4605f4..9f6be9c42 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Auth::ConfirmationsController < Devise::ConfirmationsController - include CaptchaConcern + include Auth::CaptchaConcern layout 'auth' diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 8be7c5f19..acfc0af0d 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -2,7 +2,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController include RegistrationHelper - include RegistrationSpamConcern + include Auth::RegistrationSpamConcern layout :determine_layout @@ -120,7 +120,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def require_not_suspended! - forbidden if current_account.suspended? + forbidden if current_account.unavailable? end def set_rules diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 84d9d5e11..148ad5375 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -10,7 +10,7 @@ class Auth::SessionsController < Devise::SessionsController prepend_before_action :check_suspicious!, only: [:create] - include TwoFactorAuthenticationConcern + include Auth::TwoFactorAuthenticationConcern before_action :set_body_classes diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb index 3fc0938bf..2b132417f 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -34,8 +34,8 @@ module AccountOwnedConcern end def check_account_suspension - if @account.suspended_permanently? - permanent_suspension_response + if @account.permanently_unavailable? + permanent_unavailability_response elsif @account.suspended? && !skip_temporary_suspension_response? temporary_suspension_response end @@ -45,7 +45,7 @@ module AccountOwnedConcern false end - def permanent_suspension_response + def permanent_unavailability_response expires_in(3.minutes, public: true) gone end diff --git a/app/controllers/concerns/admin_export_controller_concern.rb b/app/controllers/concerns/admin/export_controller_concern.rb similarity index 92% rename from app/controllers/concerns/admin_export_controller_concern.rb rename to app/controllers/concerns/admin/export_controller_concern.rb index 4ac48a04b..6228ae67f 100644 --- a/app/controllers/concerns/admin_export_controller_concern.rb +++ b/app/controllers/concerns/admin/export_controller_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module AdminExportControllerConcern +module Admin::ExportControllerConcern extend ActiveSupport::Concern private diff --git a/app/controllers/concerns/access_token_tracking_concern.rb b/app/controllers/concerns/api/access_token_tracking_concern.rb similarity index 92% rename from app/controllers/concerns/access_token_tracking_concern.rb rename to app/controllers/concerns/api/access_token_tracking_concern.rb index cf60cfb99..bc6ae51c7 100644 --- a/app/controllers/concerns/access_token_tracking_concern.rb +++ b/app/controllers/concerns/api/access_token_tracking_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module AccessTokenTrackingConcern +module Api::AccessTokenTrackingConcern extend ActiveSupport::Concern ACCESS_TOKEN_UPDATE_FREQUENCY = 24.hours.freeze diff --git a/app/controllers/concerns/api_caching_concern.rb b/app/controllers/concerns/api/caching_concern.rb similarity index 93% rename from app/controllers/concerns/api_caching_concern.rb rename to app/controllers/concerns/api/caching_concern.rb index 12264d514..55d7fe56d 100644 --- a/app/controllers/concerns/api_caching_concern.rb +++ b/app/controllers/concerns/api/caching_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module ApiCachingConcern +module Api::CachingConcern extend ActiveSupport::Concern def cache_if_unauthenticated! diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/api/rate_limit_headers.rb similarity index 98% rename from app/controllers/concerns/rate_limit_headers.rb rename to app/controllers/concerns/api/rate_limit_headers.rb index 5b83d8575..fe57b6f6b 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/api/rate_limit_headers.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module RateLimitHeaders +module Api::RateLimitHeaders extend ActiveSupport::Concern class_methods do diff --git a/app/controllers/concerns/captcha_concern.rb b/app/controllers/concerns/auth/captcha_concern.rb similarity index 98% rename from app/controllers/concerns/captcha_concern.rb rename to app/controllers/concerns/auth/captcha_concern.rb index 170c8f5e0..cfd93978c 100644 --- a/app/controllers/concerns/captcha_concern.rb +++ b/app/controllers/concerns/auth/captcha_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module CaptchaConcern +module Auth::CaptchaConcern extend ActiveSupport::Concern include Hcaptcha::Adapters::ViewMethods diff --git a/app/controllers/concerns/registration_spam_concern.rb b/app/controllers/concerns/auth/registration_spam_concern.rb similarity index 81% rename from app/controllers/concerns/registration_spam_concern.rb rename to app/controllers/concerns/auth/registration_spam_concern.rb index af434c985..9f4798b53 100644 --- a/app/controllers/concerns/registration_spam_concern.rb +++ b/app/controllers/concerns/auth/registration_spam_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module RegistrationSpamConcern +module Auth::RegistrationSpamConcern extend ActiveSupport::Concern def set_registration_form_time diff --git a/app/controllers/concerns/two_factor_authentication_concern.rb b/app/controllers/concerns/auth/two_factor_authentication_concern.rb similarity index 98% rename from app/controllers/concerns/two_factor_authentication_concern.rb rename to app/controllers/concerns/auth/two_factor_authentication_concern.rb index bc2d194c3..effdb8d21 100644 --- a/app/controllers/concerns/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/auth/two_factor_authentication_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TwoFactorAuthenticationConcern +module Auth::TwoFactorAuthenticationConcern extend ActiveSupport::Concern included do diff --git a/app/controllers/concerns/export_controller_concern.rb b/app/controllers/concerns/settings/export_controller_concern.rb similarity index 93% rename from app/controllers/concerns/export_controller_concern.rb rename to app/controllers/concerns/settings/export_controller_concern.rb index e1792fd6b..2cf28cced 100644 --- a/app/controllers/concerns/export_controller_concern.rb +++ b/app/controllers/concerns/settings/export_controller_concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module ExportControllerConcern +module Settings::ExportControllerConcern extend ActiveSupport::Concern included do diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 350ae2e90..8440df6b7 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -31,7 +31,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio end def require_not_suspended! - forbidden if current_account.suspended? + forbidden if current_account.unavailable? end def set_cache_headers diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 64dcd47d1..f15140aa2 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -18,6 +18,6 @@ class Settings::BaseController < ApplicationController end def require_not_suspended! - forbidden if current_account.suspended? + forbidden if current_account.unavailable? end end diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb index bb096567a..16c201b6b 100644 --- a/app/controllers/settings/deletes_controller.rb +++ b/app/controllers/settings/deletes_controller.rb @@ -25,7 +25,7 @@ class Settings::DeletesController < Settings::BaseController end def require_not_suspended! - forbidden if current_account.suspended? + forbidden if current_account.unavailable? end def challenge_passed? diff --git a/app/controllers/settings/exports/blocked_accounts_controller.rb b/app/controllers/settings/exports/blocked_accounts_controller.rb index 2190caa36..906564a3d 100644 --- a/app/controllers/settings/exports/blocked_accounts_controller.rb +++ b/app/controllers/settings/exports/blocked_accounts_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class BlockedAccountsController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/settings/exports/blocked_domains_controller.rb b/app/controllers/settings/exports/blocked_domains_controller.rb index bee4b2431..09dc52392 100644 --- a/app/controllers/settings/exports/blocked_domains_controller.rb +++ b/app/controllers/settings/exports/blocked_domains_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class BlockedDomainsController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/settings/exports/bookmarks_controller.rb b/app/controllers/settings/exports/bookmarks_controller.rb index c12e2f147..0321565b9 100644 --- a/app/controllers/settings/exports/bookmarks_controller.rb +++ b/app/controllers/settings/exports/bookmarks_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class BookmarksController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/settings/exports/following_accounts_controller.rb b/app/controllers/settings/exports/following_accounts_controller.rb index acefcb15d..0ac9031fb 100644 --- a/app/controllers/settings/exports/following_accounts_controller.rb +++ b/app/controllers/settings/exports/following_accounts_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class FollowingAccountsController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/settings/exports/lists_controller.rb b/app/controllers/settings/exports/lists_controller.rb index bc65f56a0..d90c71e24 100644 --- a/app/controllers/settings/exports/lists_controller.rb +++ b/app/controllers/settings/exports/lists_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class ListsController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/settings/exports/muted_accounts_controller.rb b/app/controllers/settings/exports/muted_accounts_controller.rb index 50b7bf1f7..e4b115890 100644 --- a/app/controllers/settings/exports/muted_accounts_controller.rb +++ b/app/controllers/settings/exports/muted_accounts_controller.rb @@ -3,7 +3,7 @@ module Settings module Exports class MutedAccountsController < BaseController - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 4748940f7..364fbf8a1 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -42,7 +42,7 @@ module WellKnown end def check_account_suspension - gone if @account.suspended_permanently? + gone if @account.permanently_unavailable? end def gone diff --git a/app/javascript/mastodon/components/dismissable_banner.tsx b/app/javascript/mastodon/components/dismissable_banner.tsx index 4feb74a3a..4e6d3bb9a 100644 --- a/app/javascript/mastodon/components/dismissable_banner.tsx +++ b/app/javascript/mastodon/components/dismissable_banner.tsx @@ -1,11 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call, + @typescript-eslint/no-unsafe-return, + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-member-access + -- the settings store is not yet typed */ import type { PropsWithChildren } from 'react'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, useEffect } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import { changeSetting } from 'mastodon/actions/settings'; import { bannerSettings } from 'mastodon/settings'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { IconButton } from './icon_button'; @@ -21,13 +28,25 @@ export const DismissableBanner: React.FC> = ({ id, children, }) => { - const [visible, setVisible] = useState(!bannerSettings.get(id)); + const dismissed = useAppSelector((state) => + state.settings.getIn(['dismissed_banners', id], false), + ); + const dispatch = useAppDispatch(); + + const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed); const intl = useIntl(); const handleDismiss = useCallback(() => { setVisible(false); bannerSettings.set(id, true); - }, [id]); + dispatch(changeSetting(['dismissed_banners', id], true)); + }, [id, dispatch]); + + useEffect(() => { + if (!visible && !dismissed) { + dispatch(changeSetting(['dismissed_banners', id], true)); + } + }, [id, dispatch, visible, dismissed]); if (!visible) { return null; diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 07d1bda0f..a605ecbb8 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -100,6 +100,15 @@ const initialState = ImmutableMap({ body: '', }), }), + + dismissed_banners: ImmutableMap({ + 'public_timeline': false, + 'community_timeline': false, + 'home.explore_prompt': false, + 'explore/links': false, + 'explore/statuses': false, + 'explore/tags': false, + }), }); const defaultColumns = fromJS([ diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 9c3d9dc2c..2106b529d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2732,22 +2732,16 @@ $ui-header-height: 55px; &__description { flex: 1 1 auto; line-height: 20px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; h6 { color: $highlight-text-color; font-weight: 500; font-size: 14px; - overflow: hidden; - text-overflow: ellipsis; } p { color: $darker-text-color; overflow: hidden; - text-overflow: ellipsis; } } } diff --git a/app/lib/account_statuses_filter.rb b/app/lib/account_statuses_filter.rb index b34ebb477..eb7592cdc 100644 --- a/app/lib/account_statuses_filter.rb +++ b/app/lib/account_statuses_filter.rb @@ -32,7 +32,7 @@ class AccountStatusesFilter private def initial_scope - return Status.none if suspended? + return Status.none if account.unavailable? if anonymous? account.statuses.where(visibility: %i(public unlisted)) @@ -70,7 +70,7 @@ class AccountStatusesFilter end def only_media_scope - Status.joins(:media_attachments).merge(account.media_attachments.reorder(nil)).group(Status.arel_table[:id]) + Status.joins(:media_attachments).merge(account.media_attachments).group(Status.arel_table[:id]) end def no_replies_scope @@ -95,10 +95,6 @@ class AccountStatusesFilter end end - def suspended? - account.suspended? - end - def anonymous? current_account.nil? end diff --git a/app/lib/activitypub/activity/move.rb b/app/lib/activitypub/activity/move.rb index 8576ceccd..7bd7e238e 100644 --- a/app/lib/activitypub/activity/move.rb +++ b/app/lib/activitypub/activity/move.rb @@ -9,7 +9,7 @@ class ActivityPub::Activity::Move < ActivityPub::Activity target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri) - if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri) + if target_account.nil? || target_account.unavailable? || !target_account.also_known_as.include?(origin_account.uri) unmark_as_processing! return end diff --git a/app/lib/content_security_policy.rb b/app/lib/content_security_policy.rb index e8fcf76a6..966e41f03 100644 --- a/app/lib/content_security_policy.rb +++ b/app/lib/content_security_policy.rb @@ -9,8 +9,8 @@ class ContentSecurityPolicy url_from_configured_asset_host || url_from_base_host end - def media_host - cdn_host_value || assets_host + def media_hosts + [assets_host, cdn_host_value].compact end private diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb index 7b21c84bb..ab7ea4092 100644 --- a/app/lib/vacuum/media_attachments_vacuum.rb +++ b/app/lib/vacuum/media_attachments_vacuum.rb @@ -27,11 +27,11 @@ class Vacuum::MediaAttachmentsVacuum end def media_attachments_past_retention_period - MediaAttachment.unscoped.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago)) + MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago)) end def orphaned_media_attachments - MediaAttachment.unscoped.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago)) + MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago)) end def retention_period? diff --git a/app/models/account.rb b/app/models/account.rb index 75f1c2575..c07b876a3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -246,6 +246,9 @@ class Account < ApplicationRecord suspended? && deletion_request.present? end + alias unavailable? suspended? + alias permanently_unavailable? suspended_permanently? + def suspend!(date: Time.now.utc, origin: :local, block_email: true) transaction do create_deletion_request! diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb index 645c2e620..4708785e7 100644 --- a/app/models/admin/status_filter.rb +++ b/app/models/admin/status_filter.rb @@ -32,7 +32,7 @@ class Admin::StatusFilter def scope_for(key, _value) case key.to_s when 'media' - Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc') + Status.joins(:media_attachments).merge(@account.media_attachments).group(:id).reorder('statuses.id desc') else raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index b567003fb..1f40e5725 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -205,12 +205,11 @@ class MediaAttachment < ApplicationRecord validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? } scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } - scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } - scope :local, -> { where(remote_url: '') } - scope :remote, -> { where.not(remote_url: '') } scope :cached, -> { remote.where.not(file_file_name: nil) } - - default_scope { order(id: :asc) } + scope :local, -> { where(remote_url: '') } + scope :ordered, -> { order(id: :asc) } + scope :remote, -> { where.not(remote_url: '') } + scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } attr_accessor :skip_download diff --git a/app/models/user.rb b/app/models/user.rb index 5185343af..b0eba97c3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -250,7 +250,7 @@ class User < ApplicationRecord end def functional_or_moved? - confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial? + confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial? end def unconfirmed? diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index f3d0ffdba..322d3aec5 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -8,7 +8,7 @@ class StatusPolicy < ApplicationPolicy end def show? - return false if author.suspended? + return false if author.unavailable? if requires_mention? owned? || mention_exists? diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 31f39954f..4ab48ff20 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -96,19 +96,19 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def discoverable - object.suspended? ? false : (object.discoverable || false) + object.unavailable? ? false : (object.discoverable || false) end def indexable - object.suspended? ? false : (object.indexable || false) + object.unavailable? ? false : (object.indexable || false) end def name - object.suspended? ? object.username : (object.display_name.presence || object.username) + object.unavailable? ? object.username : (object.display_name.presence || object.username) end def summary - object.suspended? ? '' : account_bio_format(object) + object.unavailable? ? '' : account_bio_format(object) end def icon @@ -132,23 +132,23 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def avatar_exists? - !object.suspended? && object.avatar? + !object.unavailable? && object.avatar? end def header_exists? - !object.suspended? && object.header? + !object.unavailable? && object.header? end def manually_approves_followers - object.suspended? ? false : object.locked + object.unavailable? ? false : object.locked end def virtual_tags - object.suspended? ? [] : (object.emojis + object.tags) + object.unavailable? ? [] : (object.emojis + object.tags) end def virtual_attachments - object.suspended? ? [] : object.fields + object.unavailable? ? [] : object.fields end def moved_to @@ -156,11 +156,11 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def moved? - !object.suspended? && object.moved? + !object.unavailable? && object.moved? end def also_known_as? - !object.suspended? && !object.also_known_as.empty? + !object.unavailable? && !object.also_known_as.empty? end def published diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index b707d6fcb..a8af45990 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -39,18 +39,18 @@ class InitialStateSerializer < ActiveModel::Serializer if object.current_account store[:me] = object.current_account.id.to_s - store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal - store[:boost_modal] = object.current_account.user.setting_boost_modal - store[:delete_modal] = object.current_account.user.setting_delete_modal - store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif - store[:display_media] = object.current_account.user.setting_display_media - store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers - store[:reduce_motion] = object.current_account.user.setting_reduce_motion - store[:disable_swiping] = object.current_account.user.setting_disable_swiping - store[:advanced_layout] = object.current_account.user.setting_advanced_layout - store[:use_blurhash] = object.current_account.user.setting_use_blurhash - store[:use_pending_items] = object.current_account.user.setting_use_pending_items - store[:show_trends] = Setting.trends && object.current_account.user.setting_trends + store[:unfollow_modal] = object_account_user.setting_unfollow_modal + store[:boost_modal] = object_account_user.setting_boost_modal + store[:delete_modal] = object_account_user.setting_delete_modal + store[:auto_play_gif] = object_account_user.setting_auto_play_gif + store[:display_media] = object_account_user.setting_display_media + store[:expand_spoilers] = object_account_user.setting_expand_spoilers + store[:reduce_motion] = object_account_user.setting_reduce_motion + store[:disable_swiping] = object_account_user.setting_disable_swiping + store[:advanced_layout] = object_account_user.setting_advanced_layout + store[:use_blurhash] = object_account_user.setting_use_blurhash + store[:use_pending_items] = object_account_user.setting_use_pending_items + store[:show_trends] = Setting.trends && object_account_user.setting_trends else store[:auto_play_gif] = Setting.auto_play_gif store[:display_media] = Setting.display_media @@ -71,9 +71,9 @@ class InitialStateSerializer < ActiveModel::Serializer if object.current_account store[:me] = object.current_account.id.to_s - store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy - store[:default_sensitive] = object.current_account.user.setting_default_sensitive - store[:default_language] = object.current_account.user.preferred_posting_language + store[:default_privacy] = object.visibility || object_account_user.setting_default_privacy + store[:default_sensitive] = object_account_user.setting_default_sensitive + store[:default_language] = object_account_user.preferred_posting_language end store[:text] = object.text if object.text @@ -89,11 +89,11 @@ class InitialStateSerializer < ActiveModel::Serializer associations: [:account_stat, :user, { moved_to_account: [:account_stat, :user] }] ) - store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account - store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin - store[object.owner.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner - store[object.disabled_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.disabled_account, serializer: REST::AccountSerializer) if object.disabled_account - store[object.moved_to_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.moved_to_account, serializer: REST::AccountSerializer) if object.moved_to_account + store[object.current_account.id.to_s] = serialized_account(object.current_account) if object.current_account + store[object.admin.id.to_s] = serialized_account(object.admin) if object.admin + store[object.owner.id.to_s] = serialized_account(object.owner) if object.owner + store[object.disabled_account.id.to_s] = serialized_account(object.disabled_account) if object.disabled_account + store[object.moved_to_account.id.to_s] = serialized_account(object.moved_to_account) if object.moved_to_account store end @@ -108,6 +108,14 @@ class InitialStateSerializer < ActiveModel::Serializer private + def object_account_user + object.current_account.user + end + + def serialized_account(account) + ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer) + end + def instance_presenter @instance_presenter ||= InstancePresenter.new end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 5d1292a6b..354d38446 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -61,7 +61,7 @@ class REST::AccountSerializer < ActiveModel::Serializer end def note - object.suspended? ? '' : account_bio_format(object) + object.unavailable? ? '' : account_bio_format(object) end def url @@ -73,19 +73,19 @@ class REST::AccountSerializer < ActiveModel::Serializer end def avatar - full_asset_url(object.suspended? ? object.avatar.default_url : object.avatar_original_url) + full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_original_url) end def avatar_static - full_asset_url(object.suspended? ? object.avatar.default_url : object.avatar_static_url) + full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_static_url) end def header - full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url) + full_asset_url(object.unavailable? ? object.header.default_url : object.header_original_url) end def header_static - full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url) + full_asset_url(object.unavailable? ? object.header.default_url : object.header_static_url) end def created_at @@ -97,39 +97,39 @@ class REST::AccountSerializer < ActiveModel::Serializer end def display_name - object.suspended? ? '' : object.display_name + object.unavailable? ? '' : object.display_name end def locked - object.suspended? ? false : object.locked + object.unavailable? ? false : object.locked end def bot - object.suspended? ? false : object.bot + object.unavailable? ? false : object.bot end def discoverable - object.suspended? ? false : object.discoverable + object.unavailable? ? false : object.discoverable end def indexable - object.suspended? ? false : object.indexable + object.unavailable? ? false : object.indexable end def moved_to_account - object.suspended? ? nil : AccountDecorator.new(object.moved_to_account) + object.unavailable? ? nil : AccountDecorator.new(object.moved_to_account) end def emojis - object.suspended? ? [] : object.emojis + object.unavailable? ? [] : object.emojis end def fields - object.suspended? ? [] : object.fields + object.unavailable? ? [] : object.fields end def suspended - object.suspended? + object.unavailable? end def silenced @@ -141,7 +141,7 @@ class REST::AccountSerializer < ActiveModel::Serializer end def roles - if object.suspended? || object.user.nil? + if object.unavailable? || object.user.nil? [] else [object.user.role].compact.filter(&:highlighted?) diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 3ef0366c3..886bab1eb 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -72,7 +72,7 @@ class BackupService < BaseService end def dump_media_attachments!(zipfile) - MediaAttachment.attached.where(account: account).reorder(nil).find_in_batches do |media_attachments| + MediaAttachment.attached.where(account: account).find_in_batches do |media_attachments| media_attachments.each do |m| path = m.file&.path next unless path diff --git a/app/services/clear_domain_media_service.rb b/app/services/clear_domain_media_service.rb index 7bf2d62fb..d3ad43e70 100644 --- a/app/services/clear_domain_media_service.rb +++ b/app/services/clear_domain_media_service.rb @@ -43,7 +43,7 @@ class ClearDomainMediaService < BaseService end def media_from_blocked_domain - MediaAttachment.joins(:account).merge(blocked_domain_accounts).reorder(nil) + MediaAttachment.joins(:account).merge(blocked_domain_accounts) end def emojis_from_blocked_domains diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 190a72e5c..7c7cb97df 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -165,7 +165,7 @@ class DeleteAccountService < BaseService end def purge_media_attachments! - @account.media_attachments.reorder(nil).find_each do |media_attachment| + @account.media_attachments.find_each do |media_attachment| next if keep_account_record? && reported_status_ids.include?(media_attachment.status_id) media_attachment.destroy diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 1aa0241fe..af5f99607 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -50,7 +50,7 @@ class FollowService < BaseService end def following_not_possible? - @target_account.nil? || @target_account.id == @source_account.id || @target_account.suspended? + @target_account.nil? || @target_account.id == @source_account.id || @target_account.unavailable? end def following_not_allowed? diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 125883b15..13eb20986 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -108,7 +108,7 @@ class NotifyService < BaseService end def blocked? - blocked = @recipient.suspended? + blocked = @recipient.unavailable? blocked ||= from_self? && @notification.type != :poll return blocked if message? && from_staff? diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index f3fbb8021..1c4c7805f 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -51,7 +51,7 @@ class ProcessMentionsService < BaseService # If after resolving it still isn't found or isn't the right # protocol, then give up - next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended? + next match if mention_undeliverable?(mentioned_account) || mentioned_account&.unavailable? mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id } mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } diff --git a/app/services/report_service.rb b/app/services/report_service.rb index 38e55c5b6..fe546c383 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -12,7 +12,7 @@ class ReportService < BaseService @rule_ids = options.delete(:rule_ids).presence @options = options - raise ActiveRecord::RecordNotFound if @target_account.suspended? + raise ActiveRecord::RecordNotFound if @target_account.unavailable? create_report! notify_staff! diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index e79c2d3d8..8d5446f1a 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -65,7 +65,7 @@ class SuspendAccountService < BaseService def privatize_media_attachments! attachment_names = MediaAttachment.attachment_definitions.keys - @account.media_attachments.reorder(nil).find_each do |media_attachment| + @account.media_attachments.find_each do |media_attachment| attachment_names.each do |attachment_name| attachment = media_attachment.public_send(attachment_name) styles = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys diff --git a/app/services/unsuspend_account_service.rb b/app/services/unsuspend_account_service.rb index 93cd04a94..652dd6a84 100644 --- a/app/services/unsuspend_account_service.rb +++ b/app/services/unsuspend_account_service.rb @@ -61,7 +61,7 @@ class UnsuspendAccountService < BaseService def publish_media_attachments! attachment_names = MediaAttachment.attachment_definitions.keys - @account.media_attachments.reorder(nil).find_each do |media_attachment| + @account.media_attachments.find_each do |media_attachment| attachment_names.each do |attachment_name| attachment = media_attachment.public_send(attachment_name) styles = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 755b987a8..d2f6652a0 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -1,4 +1,4 @@ -.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', (account.suspended? || account.user_unconfirmed?) && 'batch-table__row--muted'] } +.batch-table__row{ class: [!account.unavailable? && account.user_pending? && 'batch-table__row--attention', (account.unavailable? || account.user_unconfirmed?) && 'batch-table__row--muted'] } %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id .batch-table__row__content.batch-table__row__content--unpadded @@ -8,13 +8,13 @@ %td = account_link_to account, path: admin_account_path(account.id) %td.accounts-table__count.optional - - if account.suspended? || account.user_pending? + - if account.unavailable? || account.user_pending? \- - else = friendly_number_to_human account.statuses_count %small= t('accounts.posts', count: account.statuses_count).downcase %td.accounts-table__count.optional - - if account.suspended? || account.user_pending? + - if account.unavailable? || account.user_pending? \- - else = friendly_number_to_human account.followers_count @@ -30,6 +30,6 @@ \- %br/ %samp.ellipsized-ip= relevant_account_ip(account, params[:ip]) - - if !account.suspended? && account.user_pending? && account.user&.invite_request&.text.present? + - if !account.unavailable? && account.user_pending? && account.user&.invite_request&.text.present? .batch-table__row__content__quote %p= account.user&.invite_request&.text diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 40b09d87f..92e24d30c 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -27,7 +27,7 @@ = t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date)) - - unless application.superapp? || current_account.suspended? + - unless application.superapp? || current_account.unavailable? %div = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } diff --git a/app/workers/account_deletion_worker.rb b/app/workers/account_deletion_worker.rb index e4f943fbd..070352f95 100644 --- a/app/workers/account_deletion_worker.rb +++ b/app/workers/account_deletion_worker.rb @@ -7,7 +7,7 @@ class AccountDeletionWorker def perform(account_id, options = {}) account = Account.find(account_id) - return unless account.suspended? + return unless account.unavailable? reserve_username = options.with_indifferent_access.fetch(:reserve_username, true) skip_activitypub = options.with_indifferent_access.fetch(:skip_activitypub, false) diff --git a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb index 90feead67..4ea81c785 100644 --- a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb +++ b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb @@ -21,12 +21,12 @@ class Scheduler::SuspendedUserCleanupScheduler def perform return if Sidekiq::Queue.new('pull').size > MAX_PULL_SIZE - clean_suspended_accounts! + process_deletion_requests! end private - def clean_suspended_accounts! + def process_deletion_requests! # This should be fine because we only process a small amount of deletion requests at once and # `id` and `created_at` should follow the same order. AccountDeletionRequest.reorder(id: :asc).take(MAX_DELETIONS_PER_JOB).each do |deletion_request| diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 3fb80bac4..a8b61e356 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -10,7 +10,7 @@ require_relative '../../app/lib/content_security_policy' policy = ContentSecurityPolicy.new assets_host = policy.assets_host -media_host = policy.media_host +media_hosts = policy.media_hosts def sso_host return unless ENV['ONE_CLICK_SSO_LOGIN'] == 'true' @@ -35,9 +35,9 @@ Rails.application.config.content_security_policy do |p| p.default_src :none p.frame_ancestors :none p.font_src :self, assets_host - p.img_src :self, :https, :data, :blob, assets_host + p.img_src :self, :data, :blob, *media_hosts p.style_src :self, assets_host - p.media_src :self, :https, :data, assets_host + p.media_src :self, :data, *media_hosts p.frame_src :self, :https p.manifest_src :self, assets_host @@ -54,10 +54,10 @@ Rails.application.config.content_security_policy do |p| webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public]) webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" } - p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls + p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *webpacker_urls p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host else - p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url + p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url p.script_src :self, assets_host, "'wasm-unsafe-eval'" end end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 3d1750945..ba459e19f 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -13,21 +13,22 @@ # end ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym 'StatsD' - inflect.acronym 'OEmbed' - inflect.acronym 'OStatus' inflect.acronym 'ActivityPub' - inflect.acronym 'PubSubHubbub' inflect.acronym 'ActivityStreams' - inflect.acronym 'JsonLd' - inflect.acronym 'Ed25519' - inflect.acronym 'TOC' - inflect.acronym 'RSS' - inflect.acronym 'REST' - inflect.acronym 'URL' inflect.acronym 'ASCII' + inflect.acronym 'CLI' inflect.acronym 'DeepL' inflect.acronym 'DSL' + inflect.acronym 'Ed25519' + inflect.acronym 'JsonLd' + inflect.acronym 'OEmbed' + inflect.acronym 'OStatus' + inflect.acronym 'PubSubHubbub' + inflect.acronym 'REST' + inflect.acronym 'RSS' + inflect.acronym 'StatsD' + inflect.acronym 'TOC' + inflect.acronym 'URL' inflect.singular 'data', 'data' end diff --git a/spec/controllers/api/v2/search_controller_spec.rb b/spec/controllers/api/v2/search_controller_spec.rb index d3ff42d6a..a16716a10 100644 --- a/spec/controllers/api/v2/search_controller_spec.rb +++ b/spec/controllers/api/v2/search_controller_spec.rb @@ -34,6 +34,26 @@ RSpec.describe Api::V2::SearchController do expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s) end + context 'with truthy `resolve`' do + let(:params) { { q: 'test1', resolve: '1' } } + + it 'returns http unauthorized' do + get :index, params: params + + expect(response).to have_http_status(200) + end + end + + context 'with `offset`' do + let(:params) { { q: 'test1', offset: 1 } } + + it 'returns http unauthorized' do + get :index, params: params + + expect(response).to have_http_status(200) + end + end + context 'with following=true' do let(:params) { { q: 'test', type: 'accounts', following: 'true' } } @@ -48,6 +68,26 @@ RSpec.describe Api::V2::SearchController do end end end + + context 'when search raises syntax error' do + before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) } + + it 'returns http unprocessable_entity' do + get :index, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when search raises not found error' do + before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) } + + it 'returns http not_found' do + get :index, params: params + + expect(response).to have_http_status(404) + end + end end end @@ -59,6 +99,12 @@ RSpec.describe Api::V2::SearchController do get :index, params: search_params end + context 'without a `q` param' do + it 'returns http bad_request' do + expect(response).to have_http_status(400) + end + end + context 'with a `q` shorter than 5 characters' do let(:search_params) { { q: 'test' } } @@ -79,6 +125,7 @@ RSpec.describe Api::V2::SearchController do it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.body).to match('resolve remote resources') end end @@ -87,6 +134,7 @@ RSpec.describe Api::V2::SearchController do it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.body).to match('pagination is not supported') end end end diff --git a/spec/controllers/concerns/rate_limit_headers_spec.rb b/spec/controllers/concerns/api/rate_limit_headers_spec.rb similarity index 95% rename from spec/controllers/concerns/rate_limit_headers_spec.rb rename to spec/controllers/concerns/api/rate_limit_headers_spec.rb index 1cdf741f4..2050de2ae 100644 --- a/spec/controllers/concerns/rate_limit_headers_spec.rb +++ b/spec/controllers/concerns/api/rate_limit_headers_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' -describe RateLimitHeaders do +describe Api::RateLimitHeaders do controller(ApplicationController) do - include RateLimitHeaders + include Api::RateLimitHeaders def show head 200 diff --git a/spec/controllers/concerns/export_controller_concern_spec.rb b/spec/controllers/concerns/settings/export_controller_concern_spec.rb similarity index 89% rename from spec/controllers/concerns/export_controller_concern_spec.rb rename to spec/controllers/concerns/settings/export_controller_concern_spec.rb index 7f0a7c5b5..a19af8689 100644 --- a/spec/controllers/concerns/export_controller_concern_spec.rb +++ b/spec/controllers/concerns/settings/export_controller_concern_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' -describe ExportControllerConcern do +describe Settings::ExportControllerConcern do controller(ApplicationController) do - include ExportControllerConcern + include Settings::ExportControllerConcern def index send_export_file diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index a5a35e91d..1b3b0cb0a 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -20,37 +20,30 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do [true, false].each do |with_otp_secret| let(:user) { Fabricate(:user, email: 'local-part@domain', otp_secret: with_otp_secret ? 'oldotpsecret' : nil) } - describe 'GET #new' do - context 'when signed in and a new otp secret has been set in the session' do - subject do - sign_in user, scope: :user - get :new, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } + context 'when signed in' do + before { sign_in user, scope: :user } + + describe 'GET #new' do + context 'when a new otp secret has been set in the session' do + subject do + get :new, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } + end + + include_examples 'renders :new' end - include_examples 'renders :new' - end + it 'redirects if a new otp_secret has not been set in the session' do + get :new, session: { challenge_passed_at: Time.now.utc } - it 'redirects if not signed in' do - get :new - expect(response).to redirect_to('/auth/sign_in') - end - - it 'redirects if a new otp_secret has not been set in the session' do - sign_in user, scope: :user - get :new, session: { challenge_passed_at: Time.now.utc } - expect(response).to redirect_to('/settings/otp_authentication') - end - end - - describe 'POST #create' do - context 'when signed in' do - before do - sign_in user, scope: :user + expect(response).to redirect_to('/settings/otp_authentication') end + end + describe 'POST #create' do describe 'when form_two_factor_confirmation parameter is not provided' do it 'raises ActionController::ParameterMissing' do post :create, params: {}, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } + expect(response).to have_http_status(400) end end @@ -58,69 +51,78 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do describe 'when creation succeeds' do let!(:otp_backup_codes) { user.generate_otp_backup_codes! } - it 'renders page with success' do + before do prepare_user_otp_generation - prepare_user_otp_consumption + prepare_user_otp_consumption_response(true) allow(controller).to receive(:current_user).and_return(user) + end - expect do - post :create, - params: { form_two_factor_confirmation: { otp_attempt: '123456' } }, - session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } - end.to change { user.reload.otp_secret }.to 'thisisasecretforthespecofnewview' + it 'renders page with success' do + expect { post_create_with_options } + .to change { user.reload.otp_secret }.to 'thisisasecretforthespecofnewview' expect(assigns(:recovery_codes)).to eq otp_backup_codes expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled' expect(response).to have_http_status(200) expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index') end - - def prepare_user_otp_generation - allow(user) - .to receive(:generate_otp_backup_codes!) - .and_return(otp_backup_codes) - end - - def prepare_user_otp_consumption - options = { otp_secret: 'thisisasecretforthespecofnewview' } - allow(user) - .to receive(:validate_and_consume_otp!) - .with('123456', options) - .and_return(true) - end end describe 'when creation fails' do subject do - options = { otp_secret: 'thisisasecretforthespecofnewview' } - allow(user) - .to receive(:validate_and_consume_otp!) - .with('123456', options) - .and_return(false) - allow(controller).to receive(:current_user).and_return(user) - - expect do - post :create, - params: { form_two_factor_confirmation: { otp_attempt: '123456' } }, - session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } - end.to(not_change { user.reload.otp_secret }) + expect { post_create_with_options } + .to(not_change { user.reload.otp_secret }) end - it 'renders the new view' do + before do + prepare_user_otp_consumption_response(false) + allow(controller).to receive(:current_user).and_return(user) + end + + it 'renders page with error message' do subject expect(response.body).to include 'The entered code was invalid! Are server time and device time correct?' end include_examples 'renders :new' end - end - context 'when not signed in' do - it 'redirects if not signed in' do - post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } } - expect(response).to redirect_to('/auth/sign_in') + private + + def post_create_with_options + post :create, + params: { form_two_factor_confirmation: { otp_attempt: '123456' } }, + session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' } + end + + def prepare_user_otp_generation + allow(user) + .to receive(:generate_otp_backup_codes!) + .and_return(otp_backup_codes) + end + + def prepare_user_otp_consumption_response(result) + options = { otp_secret: 'thisisasecretforthespecofnewview' } + allow(user) + .to receive(:validate_and_consume_otp!) + .with('123456', options) + .and_return(result) end end end end + + context 'when not signed in' do + it 'redirects on POST to create' do + post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } } + + expect(response).to redirect_to('/auth/sign_in') + end + + it 'redirects on GET to new' do + get :new + + expect(response).to redirect_to('/auth/sign_in') + end + end end diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index 2e92f815a..4286f1498 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -59,10 +59,10 @@ describe ContentSecurityPolicy do end end - describe '#media_host' do + describe '#media_hosts' do context 'when there is no configured CDN' do it 'defaults to using the assets_host value' do - expect(subject.media_host).to eq(subject.assets_host) + expect(subject.media_hosts).to contain_exactly(subject.assets_host) end end @@ -74,7 +74,7 @@ describe ContentSecurityPolicy do end it 'uses the s3 alias host value' do - expect(subject.media_host).to eq 'https://asset-host.s3-alias.example' + expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example') end end @@ -86,7 +86,7 @@ describe ContentSecurityPolicy do end it 'uses the s3 alias host value and preserves the path' do - expect(subject.media_host).to eq 'https://asset-host.s3-alias.example/pathname/' + expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example/pathname/') end end @@ -98,7 +98,7 @@ describe ContentSecurityPolicy do end it 'uses the s3 cloudfront host value' do - expect(subject.media_host).to eq 'https://asset-host.s3-cloudfront.example' + expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-cloudfront.example') end end @@ -110,7 +110,7 @@ describe ContentSecurityPolicy do end it 'uses the azure alias host value' do - expect(subject.media_host).to eq 'https://asset-host.azure-alias.example' + expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.azure-alias.example') end end @@ -122,7 +122,7 @@ describe ContentSecurityPolicy do end it 'uses the s3 hostname host value' do - expect(subject.media_host).to eq 'https://asset-host.s3.example' + expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3.example') end end end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/requests/accounts_spec.rb similarity index 56% rename from spec/controllers/accounts_controller_spec.rb rename to spec/requests/accounts_spec.rb index 542a74878..bf067cdc3 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -2,23 +2,22 @@ require 'rails_helper' -RSpec.describe AccountsController do - render_views - +describe 'Accounts show response' do let(:account) { Fabricate(:account) } - describe 'unapproved account check' do + context 'with an unapproved account' do before { account.user.update(approved: false) } it 'returns http not found' do %w(html json rss).each do |format| - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), as: format + expect(response).to have_http_status(404) end end end - describe 'permanently suspended account check' do + context 'with a permanently suspended account' do before do account.suspend! account.deletion_request.destroy @@ -26,25 +25,26 @@ RSpec.describe AccountsController do it 'returns http gone' do %w(html json rss).each do |format| - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), as: format + expect(response).to have_http_status(410) end end end - describe 'temporarily suspended account check' do + context 'with a temporarily suspended account' do before { account.suspend! } it 'returns appropriate http response code' do { html: 403, json: 200, rss: 403 }.each do |format, code| - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), as: format expect(response).to have_http_status(code) end end end - describe 'GET #show' do + describe 'GET to short username paths' do context 'with existing statuses' do let!(:status) { Fabricate(:status, account: account) } let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) } @@ -66,17 +66,17 @@ RSpec.describe AccountsController do shared_examples 'common HTML response' do it 'returns a standard HTML response', :aggregate_failures do - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) + .and render_template(:show) expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account) - - expect(response).to render_template(:show) end end context 'with a normal account in an HTML request' do before do - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), as: format end it_behaves_like 'common HTML response' @@ -84,8 +84,7 @@ RSpec.describe AccountsController do context 'with replies' do before do - allow(controller).to receive(:replies_requested?).and_return(true) - get :show, params: { username: account.username, format: format } + get short_account_with_replies_path(username: account.username), as: format end it_behaves_like 'common HTML response' @@ -93,8 +92,7 @@ RSpec.describe AccountsController do context 'with media' do before do - allow(controller).to receive(:media_requested?).and_return(true) - get :show, params: { username: account.username, format: format } + get short_account_media_path(username: account.username), as: format end it_behaves_like 'common HTML response' @@ -106,9 +104,8 @@ RSpec.describe AccountsController do let!(:status_tag) { Fabricate(:status, account: account) } before do - allow(controller).to receive(:tag_requested?).and_return(true) status_tag.tags << tag - get :show, params: { username: account.username, format: format, tag: tag.to_param } + get short_account_tag_path(username: account.username, tag: tag), as: format end it_behaves_like 'common HTML response' @@ -117,21 +114,25 @@ RSpec.describe AccountsController do context 'with JSON' do let(:authorized_fetch_mode) { false } - let(:format) { 'json' } + let(:headers) { { 'ACCEPT' => 'application/json' } } - before do - allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode) + around do |example| + ClimateControl.modify AUTHORIZED_FETCH: authorized_fetch_mode.to_s do + example.run + end end context 'with a normal account in a JSON request' do before do - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), headers: headers end it 'returns a JSON version of the account', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(response.media_type).to eq 'application/activity+json' + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: eq('application/activity+json') + ) expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) end @@ -152,13 +153,15 @@ RSpec.describe AccountsController do before do sign_in(user) - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), headers: headers.merge({ 'Cookie' => '123' }) end it 'returns a private JSON version of the account', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(response.media_type).to eq 'application/activity+json' + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: eq('application/activity+json') + ) expect(response.headers['Cache-Control']).to include 'private' @@ -170,14 +173,15 @@ RSpec.describe AccountsController do let(:remote_account) { Fabricate(:account, domain: 'example.com') } before do - allow(controller).to receive(:signed_request_actor).and_return(remote_account) - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username), headers: headers, sign_with: remote_account end it 'returns a JSON version of the account', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(response.media_type).to eq 'application/activity+json' + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: eq('application/activity+json') + ) expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) end @@ -188,12 +192,13 @@ RSpec.describe AccountsController do let(:authorized_fetch_mode) { true } it 'returns a private signature JSON version of the account', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(response.media_type).to eq 'application/activity+json' + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: eq('application/activity+json') + ) expect(response.headers['Cache-Control']).to include 'private' - expect(response.headers['Vary']).to include 'Signature' expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) @@ -207,60 +212,58 @@ RSpec.describe AccountsController do context 'with a normal account in an RSS request' do before do - get :show, params: { username: account.username, format: format } + get short_account_path(username: account.username, format: format) end it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'responds with correct statuses', :aggregate_failures do expect(response).to have_http_status(200) - expect(response.body).to include_status_tag(status_media) - expect(response.body).to include_status_tag(status_self_reply) - expect(response.body).to include_status_tag(status) - expect(response.body).to_not include_status_tag(status_direct) - expect(response.body).to_not include_status_tag(status_private) - expect(response.body).to_not include_status_tag(status_reblog.reblog) - expect(response.body).to_not include_status_tag(status_reply) + expect(response.body).to include(status_tag_for(status_media)) + expect(response.body).to include(status_tag_for(status_self_reply)) + expect(response.body).to include(status_tag_for(status)) + expect(response.body).to_not include(status_tag_for(status_direct)) + expect(response.body).to_not include(status_tag_for(status_private)) + expect(response.body).to_not include(status_tag_for(status_reblog.reblog)) + expect(response.body).to_not include(status_tag_for(status_reply)) end end context 'with replies' do before do - allow(controller).to receive(:replies_requested?).and_return(true) - get :show, params: { username: account.username, format: format } + get short_account_with_replies_path(username: account.username, format: format) end it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'responds with correct statuses with replies', :aggregate_failures do expect(response).to have_http_status(200) - expect(response.body).to include_status_tag(status_media) - expect(response.body).to include_status_tag(status_reply) - expect(response.body).to include_status_tag(status_self_reply) - expect(response.body).to include_status_tag(status) - expect(response.body).to_not include_status_tag(status_direct) - expect(response.body).to_not include_status_tag(status_private) - expect(response.body).to_not include_status_tag(status_reblog.reblog) + expect(response.body).to include(status_tag_for(status_media)) + expect(response.body).to include(status_tag_for(status_reply)) + expect(response.body).to include(status_tag_for(status_self_reply)) + expect(response.body).to include(status_tag_for(status)) + expect(response.body).to_not include(status_tag_for(status_direct)) + expect(response.body).to_not include(status_tag_for(status_private)) + expect(response.body).to_not include(status_tag_for(status_reblog.reblog)) end end context 'with media' do before do - allow(controller).to receive(:media_requested?).and_return(true) - get :show, params: { username: account.username, format: format } + get short_account_media_path(username: account.username, format: format) end it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'responds with correct statuses with media', :aggregate_failures do expect(response).to have_http_status(200) - expect(response.body).to include_status_tag(status_media) - expect(response.body).to_not include_status_tag(status_direct) - expect(response.body).to_not include_status_tag(status_private) - expect(response.body).to_not include_status_tag(status_reblog.reblog) - expect(response.body).to_not include_status_tag(status_reply) - expect(response.body).to_not include_status_tag(status_self_reply) - expect(response.body).to_not include_status_tag(status) + expect(response.body).to include(status_tag_for(status_media)) + expect(response.body).to_not include(status_tag_for(status_direct)) + expect(response.body).to_not include(status_tag_for(status_private)) + expect(response.body).to_not include(status_tag_for(status_reblog.reblog)) + expect(response.body).to_not include(status_tag_for(status_reply)) + expect(response.body).to_not include(status_tag_for(status_self_reply)) + expect(response.body).to_not include(status_tag_for(status)) end end @@ -270,30 +273,29 @@ RSpec.describe AccountsController do let!(:status_tag) { Fabricate(:status, account: account) } before do - allow(controller).to receive(:tag_requested?).and_return(true) status_tag.tags << tag - get :show, params: { username: account.username, format: format, tag: tag.to_param } + get short_account_tag_path(username: account.username, tag: tag, format: format) end it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'responds with correct statuses with a tag', :aggregate_failures do expect(response).to have_http_status(200) - expect(response.body).to include_status_tag(status_tag) - expect(response.body).to_not include_status_tag(status_direct) - expect(response.body).to_not include_status_tag(status_media) - expect(response.body).to_not include_status_tag(status_private) - expect(response.body).to_not include_status_tag(status_reblog.reblog) - expect(response.body).to_not include_status_tag(status_reply) - expect(response.body).to_not include_status_tag(status_self_reply) - expect(response.body).to_not include_status_tag(status) + expect(response.body).to include(status_tag_for(status_tag)) + expect(response.body).to_not include(status_tag_for(status_direct)) + expect(response.body).to_not include(status_tag_for(status_media)) + expect(response.body).to_not include(status_tag_for(status_private)) + expect(response.body).to_not include(status_tag_for(status_reblog.reblog)) + expect(response.body).to_not include(status_tag_for(status_reply)) + expect(response.body).to_not include(status_tag_for(status_self_reply)) + expect(response.body).to_not include(status_tag_for(status)) end end end end end - def include_status_tag(status) - include ActivityPub::TagManager.instance.url_for(status) + def status_tag_for(status) + ActivityPub::TagManager.instance.url_for(status) end end diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb index 7eb27d61d..7610e698c 100644 --- a/spec/requests/content_security_policy_spec.rb +++ b/spec/requests/content_security_policy_spec.rb @@ -12,15 +12,15 @@ describe 'Content-Security-Policy' do "default-src 'none'", "frame-ancestors 'none'", "font-src 'self' https://cb6e6126.ngrok.io", - "img-src 'self' https: data: blob: https://cb6e6126.ngrok.io", + "img-src 'self' data: blob: https://cb6e6126.ngrok.io", "style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='", - "media-src 'self' https: data: https://cb6e6126.ngrok.io", + "media-src 'self' data: https://cb6e6126.ngrok.io", "frame-src 'self' https:", "manifest-src 'self' https://cb6e6126.ngrok.io", "form-action 'self'", "child-src 'self' blob: https://cb6e6126.ngrok.io", "worker-src 'self' blob: https://cb6e6126.ngrok.io", - "connect-src 'self' data: blob: https://cb6e6126.ngrok.io https://cb6e6126.ngrok.io ws://localhost:4000", + "connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://localhost:4000", "script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'" ) end diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 9d91f31cc..53cbaf4cc 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -384,7 +384,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end it 'updates the existing media attachment in-place' do - media_attachment = status.media_attachments.reload.first + media_attachment = status.media_attachments.ordered.reload.first expect(media_attachment).to_not be_nil expect(media_attachment.remote_url).to eq 'https://example.com/foo.png' diff --git a/yarn.lock b/yarn.lock index 69464dfa2..ad792d26c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1483,11 +1483,11 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.23.4 - resolution: "@babel/runtime@npm:7.23.4" + version: 7.23.5 + resolution: "@babel/runtime@npm:7.23.5" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: db2bf183cd0119599b903ca51ca0aeea8e0ab478a16be1aae10dd90473ed614159d3e5adfdd8f8f3d840402428ce0d90b5c01aae95da9e45a2dd83e02d85ca27 + checksum: ca679cc91bb7e424bc2db87bb58cc3b06ade916b9adb21fbbdc43e54cdaacb3eea201ceba2a0464b11d2eb65b9fe6a6ffcf4d7521fa52994f19be96f1af14788 languageName: node linkType: hard @@ -10640,8 +10640,8 @@ __metadata: linkType: hard "jsdom@npm:^23.0.0": - version: 23.0.0 - resolution: "jsdom@npm:23.0.0" + version: 23.0.1 + resolution: "jsdom@npm:23.0.1" dependencies: cssstyle: "npm:^3.0.0" data-urls: "npm:^5.0.0" @@ -10665,11 +10665,11 @@ __metadata: ws: "npm:^8.14.2" xml-name-validator: "npm:^5.0.0" peerDependencies: - canvas: ^3.0.0 + canvas: ^2.11.2 peerDependenciesMeta: canvas: optional: true - checksum: 2c876a02de49e0ed6b667a4eb9b08b8e76ac189a5571ff97791cc9564e713259314deea6d657cc7f59fc30af41b900e7d833c95017e576dfcaf25f32565722af + checksum: 13b2b3693ccb40215d1cce77bac7a295414ee4c0a06e30167f8087c9867145ba23dbd592bd95a801cadd7b3698bfd20b9c3f2c26fd8422607f22609ed2e404ef languageName: node linkType: hard