diff --git a/Gemfile b/Gemfile index 4cce095ec5..bcb19421ab 100644 --- a/Gemfile +++ b/Gemfile @@ -47,7 +47,6 @@ gem 'color_diff', '~> 0.1' gem 'csv', '~> 3.2' gem 'discard', '~> 1.2' gem 'doorkeeper', '~> 5.6' -gem 'ed25519', '~> 1.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'hiredis', '~> 0.6' diff --git a/Gemfile.lock b/Gemfile.lock index 4a139155f5..79e542014c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,8 +100,8 @@ GEM attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.974.0) - aws-sdk-core (3.205.0) + aws-partitions (1.977.0) + aws-sdk-core (3.206.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.9) @@ -109,11 +109,11 @@ GEM aws-sdk-kms (1.91.0) aws-sdk-core (~> 3, >= 3.205.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.162.0) + aws-sdk-s3 (1.163.0) aws-sdk-core (~> 3, >= 3.205.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.9.1) + aws-sigv4 (1.10.0) aws-eventstream (~> 1, >= 1.0.2) azure-storage-blob (2.0.3) azure-storage-common (~> 2.0) @@ -197,7 +197,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (5.1.0) + devise-two-factor (6.0.0) activesupport (~> 7.0) devise (~> 4.0) railties (~> 7.0) @@ -212,9 +212,8 @@ GEM domain_name (0.6.20240107) doorkeeper (5.7.1) railties (>= 5) - dotenv (3.1.2) + dotenv (3.1.4) drb (2.2.1) - ed25519 (1.3.0) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) elasticsearch-transport (= 7.17.11) @@ -429,7 +428,7 @@ GEM addressable (~> 2.5) azure-storage-blob (~> 2.0.1) hashie (~> 5.0) - memory_profiler (1.0.2) + memory_profiler (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) mime-types-data (3.2024.0820) @@ -610,7 +609,7 @@ GEM psych (5.1.2) stringio public_suffix (6.0.1) - puma (6.4.2) + puma (6.4.3) nio4r (~> 2.0) pundit (2.4.0) activesupport (>= 3.0.0) @@ -937,7 +936,6 @@ DEPENDENCIES discard (~> 1.2) doorkeeper (~> 5.6) dotenv - ed25519 (~> 1.3) email_spec fabrication (~> 2.30) faker (~> 3.2) diff --git a/app/controllers/activitypub/claims_controller.rb b/app/controllers/activitypub/claims_controller.rb deleted file mode 100644 index 480baaf2bc..0000000000 --- a/app/controllers/activitypub/claims_controller.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::ClaimsController < ActivityPub::BaseController - skip_before_action :authenticate_user! - - before_action :require_account_signature! - before_action :set_claim_result - - def create - render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer - end - - private - - def set_claim_result - @claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id]) - end -end diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 15985c7f65..e9779260f3 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -22,8 +22,6 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController @items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) } when 'tags' @items = for_signed_account { @account.featured_tags } - when 'devices' - @items = @account.devices else not_found end @@ -31,7 +29,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController def set_size case params[:id] - when 'featured', 'devices', 'tags' + when 'featured', 'tags' @size = @items.size else not_found @@ -42,7 +40,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController case params[:id] when 'featured' @type = :ordered - when 'devices', 'tags' + when 'tags' @type = :unordered else not_found diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index 66da65beda..b7f22824a7 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -7,7 +7,7 @@ class Api::OEmbedController < Api::BaseController before_action :require_public_status! def show - render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default + render json: @status, serializer: OEmbedSerializer, width: params[:maxwidth], height: params[:maxheight] end private @@ -23,12 +23,4 @@ class Api::OEmbedController < Api::BaseController def status_finder StatusFinder.new(params[:url]) end - - def maxwidth_or_default - (params[:maxwidth].presence || 400).to_i - end - - def maxheight_or_default - params[:maxheight].present? ? params[:maxheight].to_i : nil - end end diff --git a/app/controllers/api/v1/crypto/deliveries_controller.rb b/app/controllers/api/v1/crypto/deliveries_controller.rb deleted file mode 100644 index aa9df6e03b..0000000000 --- a/app/controllers/api/v1/crypto/deliveries_controller.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::DeliveriesController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - def create - devices.each do |device_params| - DeliverToDeviceService.new.call(current_account, @current_device, device_params) - end - - render_empty - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end - - def resource_params - params.require(:device) - params.permit(device: [:account_id, :device_id, :type, :body, :hmac]) - end - - def devices - Array(resource_params[:device]) - end -end diff --git a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb b/app/controllers/api/v1/crypto/encrypted_messages_controller.rb deleted file mode 100644 index 93ae0e7771..0000000000 --- a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController - LIMIT = 80 - - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - before_action :set_encrypted_messages, only: :index - after_action :insert_pagination_headers, only: :index - - def index - render json: @encrypted_messages, each_serializer: REST::EncryptedMessageSerializer - end - - def clear - @current_device.encrypted_messages.up_to(params[:up_to_id]).delete_all - render_empty - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end - - def set_encrypted_messages - @encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) - end - - def next_path - api_v1_crypto_encrypted_messages_url pagination_params(max_id: pagination_max_id) if records_continue? - end - - def prev_path - api_v1_crypto_encrypted_messages_url pagination_params(min_id: pagination_since_id) unless @encrypted_messages.empty? - end - - def pagination_collection - @encrypted_messages - end - - def records_continue? - @encrypted_messages.size == limit_param(LIMIT) - end -end diff --git a/app/controllers/api/v1/crypto/keys/claims_controller.rb b/app/controllers/api/v1/crypto/keys/claims_controller.rb deleted file mode 100644 index f9d202d67b..0000000000 --- a/app/controllers/api/v1/crypto/keys/claims_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_claim_results - - def create - render json: @claim_results, each_serializer: REST::Keys::ClaimResultSerializer - end - - private - - def set_claim_results - @claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) } - end - - def resource_params - params.permit(device: [:account_id, :device_id]) - end - - def devices - Array(resource_params[:device]) - end -end diff --git a/app/controllers/api/v1/crypto/keys/counts_controller.rb b/app/controllers/api/v1/crypto/keys/counts_controller.rb deleted file mode 100644 index ffd7151b78..0000000000 --- a/app/controllers/api/v1/crypto/keys/counts_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::CountsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_current_device - - def show - render json: { one_time_keys: @current_device.one_time_keys.count } - end - - private - - def set_current_device - @current_device = Device.find_by!(access_token: doorkeeper_token) - end -end diff --git a/app/controllers/api/v1/crypto/keys/queries_controller.rb b/app/controllers/api/v1/crypto/keys/queries_controller.rb deleted file mode 100644 index e6ce9f9192..0000000000 --- a/app/controllers/api/v1/crypto/keys/queries_controller.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::QueriesController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - before_action :set_accounts - before_action :set_query_results - - def create - render json: @query_results, each_serializer: REST::Keys::QueryResultSerializer - end - - private - - def set_accounts - @accounts = Account.where(id: account_ids).includes(:devices) - end - - def set_query_results - @query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) } - end - - def account_ids - Array(params[:id]).map(&:to_i) - end -end diff --git a/app/controllers/api/v1/crypto/keys/uploads_controller.rb b/app/controllers/api/v1/crypto/keys/uploads_controller.rb deleted file mode 100644 index fc4abf63b3..0000000000 --- a/app/controllers/api/v1/crypto/keys/uploads_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Crypto::Keys::UploadsController < Api::BaseController - before_action -> { doorkeeper_authorize! :crypto } - before_action :require_user! - - def create - device = Device.find_or_initialize_by(access_token: doorkeeper_token) - - device.transaction do - device.account = current_account - device.update!(resource_params[:device]) - - if resource_params[:one_time_keys].present? && resource_params[:one_time_keys].is_a?(Enumerable) - resource_params[:one_time_keys].each do |one_time_key_params| - device.one_time_keys.create!(one_time_key_params) - end - end - end - - render json: device, serializer: REST::Keys::DeviceSerializer - end - - private - - def resource_params - params.permit(device: [:device_id, :name, :fingerprint_key, :identity_key], one_time_keys: [:key_id, :key, :signature]) - end -end diff --git a/app/controllers/api/v1/peers/search_controller.rb b/app/controllers/api/v1/peers/search_controller.rb index 1780554c5d..d9c8232702 100644 --- a/app/controllers/api/v1/peers/search_controller.rb +++ b/app/controllers/api/v1/peers/search_controller.rb @@ -7,6 +7,8 @@ class Api::V1::Peers::SearchController < Api::BaseController skip_before_action :require_authenticated_user!, unless: :limited_federation_mode? skip_around_action :set_locale + LIMIT = 10 + vary_by '' def index @@ -35,10 +37,10 @@ class Api::V1::Peers::SearchController < Api::BaseController field: 'accounts_count', modifier: 'log2p', }, - }).limit(10).pluck(:domain) + }).limit(LIMIT).pluck(:domain) else domain = normalized_domain - @domains = Instance.searchable.domain_starts_with(domain).limit(10).pluck(:domain) + @domains = Instance.searchable.domain_starts_with(domain).limit(LIMIT).pluck(:domain) end rescue Addressable::URI::InvalidURIError @domains = [] diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index 63c3f2d90a..f82c1c50d7 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -9,7 +9,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController return not_found if @status.hidden? if @status.local? - render json: @status, serializer: OEmbedSerializer, width: 400 + render json: @status, serializer: OEmbedSerializer else return not_found unless user_signed_in? diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index a2fed644fe..ecac4c5ba8 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -20,11 +20,6 @@ class Auth::SessionsController < Devise::SessionsController p.form_action(false) end - def check_suspicious! - user = find_user - @login_is_suspicious = suspicious_sign_in?(user) unless user.nil? - end - def create super do |resource| # We only need to call this if this hasn't already been @@ -101,6 +96,11 @@ class Auth::SessionsController < Devise::SessionsController private + def check_suspicious! + user = find_user + @login_is_suspicious = suspicious_sign_in?(user) unless user.nil? + end + def home_paths(resource) paths = [about_path, '/explore'] diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 71e19207d0..b36e25c092 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -24,23 +24,6 @@ module ContextHelper indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' }, memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' }, voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, - olm: { - 'toot' => 'http://joinmastodon.org/ns#', - 'Device' => 'toot:Device', - 'Ed25519Signature' => 'toot:Ed25519Signature', - 'Ed25519Key' => 'toot:Ed25519Key', - 'Curve25519Key' => 'toot:Curve25519Key', - 'EncryptedMessage' => 'toot:EncryptedMessage', - 'publicKeyBase64' => 'toot:publicKeyBase64', - 'deviceId' => 'toot:deviceId', - 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, - 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, - 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, - 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, - 'messageFranking' => 'toot:messageFranking', - 'messageType' => 'toot:messageType', - 'cipherText' => 'toot:cipherText', - }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } }, }.freeze diff --git a/app/javascript/hooks/useSearchParam.ts b/app/javascript/hooks/useSearchParam.ts new file mode 100644 index 0000000000..2df8c0b3a9 --- /dev/null +++ b/app/javascript/hooks/useSearchParam.ts @@ -0,0 +1,31 @@ +import { useMemo, useCallback } from 'react'; + +import { useLocation, useHistory } from 'react-router'; + +export function useSearchParams() { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +} + +export function useSearchParam(name: string, defaultValue?: string) { + const searchParams = useSearchParams(); + const history = useHistory(); + + const value = searchParams.get(name) ?? defaultValue; + + const setValue = useCallback( + (value: string | null) => { + if (value === null) { + searchParams.delete(name); + } else { + searchParams.set(name, value); + } + + history.push({ search: searchParams.toString() }); + }, + [history, name, searchParams], + ); + + return [value, setValue] as const; +} diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 42834146bf..48dee2587f 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -1,5 +1,7 @@ import { defineMessages } from 'react-intl'; +import { AxiosError } from 'axios'; + const messages = defineMessages({ unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, @@ -50,6 +52,11 @@ export const showAlertForError = (error, skipNotFound = false) => { }); } + // An aborted request, e.g. due to reloading the browser window, it not really error + if (error.code === AxiosError.ECONNABORTED) { + return { type: ALERT_NOOP }; + } + console.error(error); return showAlert({ diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index 24672290c7..25bb25547c 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -42,6 +42,9 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => { // eslint-disable-next-line import/no-default-export export default function api(withAuthorization = true) { return axios.create({ + transitional: { + clarifyTimeoutError: true, + }, headers: { ...csrfHeader, ...(withAuthorization ? authorizationTokenFromInitialState() : {}), diff --git a/app/javascript/mastodon/components/modal_root.jsx b/app/javascript/mastodon/components/modal_root.jsx index fd13564af2..e7fa5e6f9a 100644 --- a/app/javascript/mastodon/components/modal_root.jsx +++ b/app/javascript/mastodon/components/modal_root.jsx @@ -148,7 +148,7 @@ class ModalRoot extends PureComponent { return (
-
+
{children}
diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx index 33fb60abb7..558d0307e7 100644 --- a/app/javascript/mastodon/components/router.tsx +++ b/app/javascript/mastodon/components/router.tsx @@ -51,7 +51,8 @@ function normalizePath( if ( layoutFromWindow() === 'multi-column' && - !location.pathname?.startsWith('/deck') + location.pathname && + !location.pathname.startsWith('/deck') ) { location.pathname = `/deck${location.pathname}`; } diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index 51d283a482..d0e57600bb 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler } from 'react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -23,6 +23,8 @@ import { RadioButton } from 'mastodon/components/radio_button'; import ScrollContainer from 'mastodon/containers/scroll_container'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; +import { useSearchParam } from '../../../hooks/useSearchParam'; + import { AccountCard } from './components/account_card'; const messages = defineMessages({ @@ -47,18 +49,19 @@ export const Directory: React.FC<{ const intl = useIntl(); const dispatch = useAppDispatch(); - const [state, setState] = useState<{ - order: string | null; - local: boolean | null; - }>({ - order: null, - local: null, - }); - const column = useRef(null); - const order = state.order ?? params?.order ?? 'active'; - const local = state.local ?? params?.local ?? false; + const [orderParam, setOrderParam] = useSearchParam('order'); + const [localParam, setLocalParam] = useSearchParam('local'); + + let localParamBool: boolean | undefined; + + if (localParam === 'false') { + localParamBool = false; + } + + const order = orderParam ?? params?.order ?? 'active'; + const local = localParamBool ?? params?.local ?? true; const handlePin = useCallback(() => { if (columnId) { @@ -101,10 +104,10 @@ export const Directory: React.FC<{ if (columnId) { dispatch(changeColumnParams(columnId, ['order'], e.target.value)); } else { - setState((s) => ({ order: e.target.value, local: s.local })); + setOrderParam(e.target.value); } }, - [dispatch, columnId], + [dispatch, columnId, setOrderParam], ); const handleChangeLocal = useCallback>( @@ -113,11 +116,13 @@ export const Directory: React.FC<{ dispatch( changeColumnParams(columnId, ['local'], e.target.value === '1'), ); + } else if (e.target.value === '1') { + setLocalParam('true'); } else { - setState((s) => ({ local: e.target.value === '1', order: s.order })); + setLocalParam('false'); } }, - [dispatch, columnId], + [dispatch, columnId, setLocalParam], ); const handleLoadMore = useCallback(() => { diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 5a2ea8c2c8..ff76d5bcb2 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -4,8 +4,6 @@ import { Children, cloneElement, useCallback } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { supportsPassiveEvents } from 'detect-passive-events'; - import { scrollRight } from '../../../scroll'; import BundleContainer from '../containers/bundle_container'; import { @@ -71,10 +69,6 @@ export default class ColumnsArea extends ImmutablePureComponent { }; componentDidMount() { - if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - if (this.mediaQuery) { if (this.mediaQuery.addEventListener) { this.mediaQuery.addEventListener('change', this.handleLayoutChange); @@ -87,23 +81,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); } - UNSAFE_componentWillUpdate(nextProps) { - if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - } - - componentDidUpdate(prevProps) { - if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - } - componentWillUnmount () { - if (!this.props.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - if (this.mediaQuery) { if (this.mediaQuery.removeEventListener) { this.mediaQuery.removeEventListener('change', this.handleLayoutChange); @@ -116,7 +94,7 @@ export default class ColumnsArea extends ImmutablePureComponent { handleChildrenContentChange() { if (!this.props.singleColumn) { const modifier = this.isRtlLayout ? -1 : 1; - this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); } } @@ -124,14 +102,6 @@ export default class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); }; - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - setRef = (node) => { this.node = node; }; diff --git a/app/javascript/mastodon/features/ui/components/image_loader.jsx b/app/javascript/mastodon/features/ui/components/image_loader.jsx index 9dabc621b4..b1417deda7 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.jsx +++ b/app/javascript/mastodon/features/ui/components/image_loader.jsx @@ -17,7 +17,7 @@ export default class ImageLoader extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -134,7 +134,7 @@ export default class ImageLoader extends PureComponent { }; render () { - const { alt, lang, src, width, height, onClick } = this.props; + const { alt, lang, src, width, height, onClick, zoomedIn } = this.props; const { loading } = this.state; const className = classNames('image-loader', { @@ -149,6 +149,7 @@ export default class ImageLoader extends PureComponent {
+ )}
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx index 0f6e8a727b..d69ceba539 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -12,6 +12,8 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import FitScreenIcon from '@/material-icons/400-24px/fit_screen.svg?react'; +import ActualSizeIcon from '@/svg-icons/actual_size.svg?react'; import { getAverageFromBlurhash } from 'mastodon/blurhash'; import { GIFV } from 'mastodon/components/gifv'; import { Icon } from 'mastodon/components/icon'; @@ -26,6 +28,8 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, + zoomIn: { id: 'lightbox.zoom_in', defaultMessage: 'Zoom to actual size' }, + zoomOut: { id: 'lightbox.zoom_out', defaultMessage: 'Zoom to fit' }, }); class MediaModal extends ImmutablePureComponent { @@ -46,30 +50,39 @@ class MediaModal extends ImmutablePureComponent { state = { index: null, navigationHidden: false, - zoomButtonHidden: false, + zoomedIn: false, + }; + + handleZoomClick = () => { + this.setState(prevState => ({ + zoomedIn: !prevState.zoomedIn, + })); }; handleSwipe = (index) => { - this.setState({ index: index % this.props.media.size }); + this.setState({ + index: index % this.props.media.size, + zoomedIn: false, + }); }; handleTransitionEnd = () => { this.setState({ - zoomButtonHidden: false, + zoomedIn: false, }); }; handleNextClick = () => { this.setState({ index: (this.getIndex() + 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; handlePrevClick = () => { this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -78,7 +91,7 @@ class MediaModal extends ImmutablePureComponent { this.setState({ index: index % this.props.media.size, - zoomButtonHidden: true, + zoomedIn: false, }); }; @@ -130,15 +143,22 @@ class MediaModal extends ImmutablePureComponent { return this.state.index !== null ? this.state.index : this.props.index; } - toggleNavigation = () => { + handleToggleNavigation = () => { this.setState(prevState => ({ navigationHidden: !prevState.navigationHidden, })); }; + setRef = c => { + this.setState({ + viewportWidth: c?.clientWidth, + viewportHeight: c?.clientHeight, + }); + }; + render () { const { media, statusId, lang, intl, onClose } = this.props; - const { navigationHidden } = this.state; + const { navigationHidden, zoomedIn, viewportWidth, viewportHeight } = this.state; const index = this.getIndex(); @@ -160,8 +180,8 @@ class MediaModal extends ImmutablePureComponent { alt={description} lang={lang} key={image.get('url')} - onClick={this.toggleNavigation} - zoomButtonHidden={this.state.zoomButtonHidden} + onClick={this.handleToggleNavigation} + zoomedIn={zoomedIn} /> ); } else if (image.get('type') === 'video') { @@ -230,9 +250,12 @@ class MediaModal extends ImmutablePureComponent { )); } + const currentMedia = media.get(index); + const zoomable = currentMedia.get('type') === 'image' && (currentMedia.getIn(['meta', 'original', 'width']) > viewportWidth || currentMedia.getIn(['meta', 'original', 'height']) > viewportHeight); + return ( -
-
+
+
- +
+ {zoomable && } + +
{leftNav} {rightNav} diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx index 272a3cff00..c4129bf260 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx @@ -1,17 +1,6 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; -import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; -import { IconButton } from 'mastodon/components/icon_button'; - -const messages = defineMessages({ - compress: { id: 'lightbox.compress', defaultMessage: 'Compress image view box' }, - expand: { id: 'lightbox.expand', defaultMessage: 'Expand image view box' }, -}); - const MIN_SCALE = 1; const MAX_SCALE = 4; const NAV_BAR_HEIGHT = 66; @@ -104,8 +93,7 @@ class ZoomableImage extends PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, - zoomButtonHidden: PropTypes.bool, - intl: PropTypes.object.isRequired, + zoomedIn: PropTypes.bool, }; static defaultProps = { @@ -131,8 +119,6 @@ class ZoomableImage extends PureComponent { translateX: null, translateY: null, }, - zoomState: 'expand', // 'expand' 'compress' - navigationHidden: false, dragPosition: { top: 0, left: 0, x: 0, y: 0 }, dragged: false, lockScroll: { x: 0, y: 0 }, @@ -169,35 +155,20 @@ class ZoomableImage extends PureComponent { this.container.addEventListener('DOMMouseScroll', handler); this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler)); - this.initZoomMatrix(); + this._initZoomMatrix(); } componentWillUnmount () { - this.removeEventListeners(); + this._removeEventListeners(); } - componentDidUpdate () { - this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); - - if (this.state.scale === MIN_SCALE) { - this.container.style.removeProperty('cursor'); + componentDidUpdate (prevProps) { + if (prevProps.zoomedIn !== this.props.zoomedIn) { + this._toggleZoom(); } } - UNSAFE_componentWillReceiveProps () { - // reset when slide to next image - if (this.props.zoomButtonHidden) { - this.setState({ - scale: MIN_SCALE, - lockTranslate: { x: 0, y: 0 }, - }, () => { - this.container.scrollLeft = 0; - this.container.scrollTop = 0; - }); - } - } - - removeEventListeners () { + _removeEventListeners () { this.removers.forEach(listeners => listeners()); this.removers = []; } @@ -220,9 +191,6 @@ class ZoomableImage extends PureComponent { }; mouseDownHandler = e => { - this.container.style.cursor = 'grabbing'; - this.container.style.userSelect = 'none'; - this.setState({ dragPosition: { left: this.container.scrollLeft, top: this.container.scrollTop, @@ -246,9 +214,6 @@ class ZoomableImage extends PureComponent { }; mouseUpHandler = () => { - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - this.image.removeEventListener('mousemove', this.mouseMoveHandler); this.image.removeEventListener('mouseup', this.mouseUpHandler); }; @@ -276,13 +241,13 @@ class ZoomableImage extends PureComponent { const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate); const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance); - this.zoom(scale, midpoint); + this._zoom(scale, midpoint); this.lastMidpoint = midpoint; this.lastDistance = distance; }; - zoom(nextScale, midpoint) { + _zoom(nextScale, midpoint) { const { scale, zoomMatrix } = this.state; const { scrollLeft, scrollTop } = this.container; @@ -318,14 +283,13 @@ class ZoomableImage extends PureComponent { if (dragged) return; const handler = this.props.onClick; if (handler) handler(); - this.setState({ navigationHidden: !this.state.navigationHidden }); }; handleMouseDown = e => { e.preventDefault(); }; - initZoomMatrix = () => { + _initZoomMatrix = () => { const { width, height } = this.props; const { clientWidth, clientHeight } = this.container; const { offsetWidth, offsetHeight } = this.image; @@ -357,10 +321,7 @@ class ZoomableImage extends PureComponent { }); }; - handleZoomClick = e => { - e.preventDefault(); - e.stopPropagation(); - + _toggleZoom () { const { scale, zoomMatrix } = this.state; if ( scale >= zoomMatrix.rate ) { @@ -394,10 +355,7 @@ class ZoomableImage extends PureComponent { this.container.scrollTop = zoomMatrix.scrollTop; }); } - - this.container.style.cursor = 'grab'; - this.container.style.removeProperty('user-select'); - }; + } setContainerRef = c => { this.container = c; @@ -408,52 +366,37 @@ class ZoomableImage extends PureComponent { }; render () { - const { alt, lang, src, width, height, intl } = this.props; - const { scale, lockTranslate } = this.state; + const { alt, lang, src, width, height } = this.props; + const { scale, lockTranslate, dragged } = this.state; const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; - const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; - const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); + const cursor = scale === MIN_SCALE ? null : (dragged ? 'grabbing' : 'grab'); return ( - <> - + {alt} -
- {alt} -
- +
); } - } -export default injectIntl(ZoomableImage); +export default ZoomableImage; diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 43256506cd..d50ca8dbb5 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -36,6 +36,7 @@ "account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.", "account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}", "account.following": "الاشتراكات", + "account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}", "account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.", "account.go_to_profile": "اذهب إلى الملف الشخصي", "account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}", @@ -309,7 +310,7 @@ "follow_request.authorize": "ترخيص", "follow_request.reject": "رفض", "follow_requests.unlocked_explanation": "حتى وإن كان حسابك غير مقفل، يعتقد فريق {domain} أنك قد ترغب في مراجعة طلبات المتابعة من هذه الحسابات يدوياً.", - "follow_suggestions.curated_suggestion": "اختيار الموظفين", + "follow_suggestions.curated_suggestion": "انتقاه الفريق", "follow_suggestions.dismiss": "لا تُظهرها مجدّدًا", "follow_suggestions.featured_longer": "مختار يدوياً من قِبل فريق {domain}", "follow_suggestions.friends_of_friends_longer": "مشهور بين الأشخاص الذين تتابعهم", @@ -752,7 +753,7 @@ "status.edit": "تعديل", "status.edited": "آخر تعديل يوم {date}", "status.edited_x_times": "عُدّل {count, plural, zero {} one {مرةً واحدة} two {مرّتان} few {{count} مرات} many {{count} مرة} other {{count} مرة}}", - "status.embed": "الحصول على شفرة الإدماج", + "status.embed": "الحصول على شيفرة الدمج", "status.favourite": "فضّل", "status.favourites": "{count, plural, zero {}one {مفضلة واحدة} two {مفضلتان} few {# مفضلات} many {# مفضلات} other {# مفضلات}}", "status.filter": "تصفية هذا المنشور", @@ -773,7 +774,7 @@ "status.reblog": "إعادة النشر", "status.reblog_private": "إعادة النشر إلى الجمهور الأصلي", "status.reblogged_by": "شارَكَه {name}", - "status.reblogs": "{count, plural, one {تعزيز واحد} two {تعزيزتان} few {# تعزيزات} many {# تعزيزات} other {# تعزيزات}}", + "status.reblogs": "{count, plural, one {إعادة نشر واحدة} two {معاد نشرها مرتان} few {# إعادات نشر} many {# إعادات نشر} other {# إعادة نشر}}", "status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.", "status.redraft": "إزالة وإعادة الصياغة", "status.remove_bookmark": "احذفه مِن الفواصل المرجعية", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 7f2e88d440..d0563bb1b2 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -432,10 +432,10 @@ "keyboard_shortcuts.unfocus": "Unfocus compose textarea/search", "keyboard_shortcuts.up": "Move up in the list", "lightbox.close": "Close", - "lightbox.compress": "Compress image view box", - "lightbox.expand": "Expand image view box", "lightbox.next": "Next", "lightbox.previous": "Previous", + "lightbox.zoom_in": "Zoom to actual size", + "lightbox.zoom_out": "Zoom to fit", "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "link_preview.author": "By {name}", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index ce7201dacf..e162e732d0 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -97,6 +97,8 @@ "block_modal.title": "Ĉu bloki uzanton?", "block_modal.you_wont_see_mentions": "Vi ne vidos afiŝojn, ke mencii ilin.", "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje", + "boost_modal.reblog": "Ĉu diskonigi afiŝon?", + "boost_modal.undo_reblog": "Ĉu ĉesi diskonigi afiŝon?", "bundle_column_error.copy_stacktrace": "Kopii la eraran raporton", "bundle_column_error.error.body": "La petita paĝo ne povas redonitis. Eble estas eraro.", "bundle_column_error.error.title": "Ho, ve!", @@ -188,8 +190,12 @@ "confirmations.redraft.title": "Ĉu forigi kaj redakcii afiŝon?", "confirmations.reply.confirm": "Respondi", "confirmations.reply.message": "Respondi nun anstataŭigos la skribatan afiŝon. Ĉu vi certas, ke vi volas daŭrigi?", + "confirmations.reply.title": "Ĉu superskribi afiŝon?", "confirmations.unfollow.confirm": "Ne plu sekvi", "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?", + "confirmations.unfollow.title": "Ĉu ĉesi sekvi uzanton?", + "content_warning.hide": "Kaŝi afiŝon", + "content_warning.show": "Montri ĉiukaze", "conversation.delete": "Forigi konversacion", "conversation.mark_as_read": "Marki legita", "conversation.open": "Vidi konversacion", @@ -209,6 +215,8 @@ "dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.", "dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.", "dismissable_banner.public_timeline": "Ĉi tioj estas plej lastaj publikaj afiŝoj de personoj ĉe socia reto kiu personoj ĉe {domain} sekvas.", + "domain_block_modal.they_cant_follow": "Neniu el ĉi tiu servilo povas sekvi vin.", + "domain_pill.username": "Uzantnomo", "embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.", "embed.preview": "Ĝi aperos tiel:", "emoji_button.activity": "Agadoj", @@ -281,6 +289,12 @@ "follow_request.authorize": "Rajtigi", "follow_request.reject": "Rifuzi", "follow_requests.unlocked_explanation": "Kvankam via konto ne estas ŝlosita, la dungitaro de {domain} opinias, ke vi eble volas revizii petojn pri sekvado de ĉi tiuj kontoj permane.", + "follow_suggestions.dismiss": "Ne montri denove", + "follow_suggestions.hints.friends_of_friends": "Ĉi tiu profilo estas populara inter la homoj, kiujn vi sekvas.", + "follow_suggestions.hints.most_followed": "Ĉi tiu profilo estas unu el la plej sekvataj en {domain}.", + "follow_suggestions.popular_suggestion_longer": "Populara en {domain}", + "follow_suggestions.view_all": "Vidi ĉiujn", + "follow_suggestions.who_to_follow": "Kiun sekvi", "followed_tags": "Sekvataj kradvortoj", "footer.about": "Pri", "footer.directory": "Profilujo", @@ -374,6 +388,7 @@ "limited_account_hint.action": "Montru profilon ĉiukaze", "limited_account_hint.title": "La profilo estas kaŝita de la moderigantoj de {domain}.", "link_preview.author": "De {name}", + "link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}", "lists.account.add": "Aldoni al la listo", "lists.account.remove": "Forigi de la listo", "lists.delete": "Forigi la liston", @@ -390,8 +405,12 @@ "lists.subheading": "Viaj listoj", "load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}", "loading_indicator.label": "Ŝargado…", + "media_gallery.hide": "Kaŝi", "moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.", + "mute_modal.show_options": "Montri agordojn", + "mute_modal.they_can_mention_and_follow": "Ili povas mencii kaj sekvi vin, sed vi ne vidos ilin.", "navigation_bar.about": "Pri", + "navigation_bar.administration": "Administrado", "navigation_bar.advanced_interface": "Malfermi altnivelan retpaĝan interfacon", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.bookmarks": "Legosignoj", @@ -422,10 +441,18 @@ "notification.favourite": "{name} stelumis vian afiŝon", "notification.follow": "{name} eksekvis vin", "notification.follow_request": "{name} petis sekvi vin", + "notification.label.mention": "Mencii", + "notification.label.private_mention": "Privata mencio", + "notification.label.private_reply": "Privata respondo", + "notification.label.reply": "Respondi", + "notification.mention": "Mencii", + "notification.moderation-warning.learn_more": "Lerni pli", "notification.own_poll": "Via enketo finiĝis", "notification.reblog": "{name} diskonigis vian afiŝon", + "notification.relationships_severance_event.learn_more": "Lerni pli", "notification.status": "{name} ĵus afiŝis", "notification.update": "{name} redaktis afiŝon", + "notification_requests.accept": "Akcepti", "notifications.clear": "Forviŝi sciigojn", "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?", "notifications.column_settings.admin.report": "Novaj raportoj:", @@ -457,6 +484,8 @@ "notifications.permission_denied": "Labortablaj sciigoj ne disponeblas pro peto antaŭe rifuzita de retumiloj", "notifications.permission_denied_alert": "Labortablaj sciigoj ne povas esti ebligitaj, ĉar retumilpermeso antaŭe estis rifuzita", "notifications.permission_required": "Labortablaj sciigoj ne disponeblas ĉar la bezonata permeso ne estis donita.", + "notifications.policy.accept": "Akcepti", + "notifications.policy.filter_new_accounts_title": "Novaj kontoj", "notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn", "notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.", "notifications_permission_banner.title": "Neniam preterlasas iun ajn", @@ -581,6 +610,7 @@ "report_notification.attached_statuses": "{count, plural, one {{count} afiŝo almetita} other {{count} afiŝoj almetitaj}}", "report_notification.categories.legal": "Laŭleĝa", "report_notification.categories.other": "Alia", + "report_notification.categories.other_sentence": "alia", "report_notification.categories.spam": "Trudmesaĝo", "report_notification.categories.violation": "Malobservo de la regulo", "report_notification.open": "Malfermi la raporton", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index d1abc392bf..5c8f44d4a7 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -39,11 +39,11 @@ "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavía no sigue a nadie.", "account.go_to_profile": "Ir al perfil", - "account.hide_reblogs": "Ocultar retoots de @{name}", + "account.hide_reblogs": "Ocultar impulsos de @{name}", "account.in_memoriam": "En memoria.", "account.joined_short": "Se unió", "account.languages": "Cambiar idiomas suscritos", - "account.link_verified_on": "El proprietario de este link fue comprobado el {date}", + "account.link_verified_on": "El proprietario de este enlace fue comprobado el {date}", "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.", "account.media": "Multimedia", "account.mention": "Mencionar a @{name}", @@ -61,7 +61,7 @@ "account.requested": "Esperando aprobación. Haga clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", - "account.show_reblogs": "Mostrar retoots de @{name}", + "account.show_reblogs": "Mostrar impulsos de @{name}", "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Mostrar a {domain}", @@ -70,8 +70,8 @@ "account.unfollow": "Dejar de seguir", "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", - "account.unmute_short": "Desmutear", - "account_note.placeholder": "Clic para añadir nota", + "account.unmute_short": "Dejar de silenciar", + "account_note.placeholder": "Haz clic para agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después de unirse", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después de unirse", "admin.dashboard.retention.average": "Promedio", @@ -97,7 +97,7 @@ "block_modal.title": "¿Bloquear usuario?", "block_modal.you_wont_see_mentions": "No verás publicaciones que los mencionen.", "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez", - "boost_modal.reblog": "¿Impulsar la publicación?", + "boost_modal.reblog": "¿Deseas impulsar la publicación?", "boost_modal.undo_reblog": "¿Dejar de impulsar la publicación?", "bundle_column_error.copy_stacktrace": "Copiar informe de error", "bundle_column_error.error.body": "La página solicitada no pudo ser renderizada. Podría deberse a un error en nuestro código o a un problema de compatibilidad con el navegador.", @@ -130,7 +130,7 @@ "column.lists": "Listas", "column.mutes": "Usuarios silenciados", "column.notifications": "Notificaciones", - "column.pins": "Toots fijados", + "column.pins": "Publicaciones fijadas", "column.public": "Línea de tiempo federada", "column_back_button.label": "Atrás", "column_header.hide_settings": "Ocultar configuración", @@ -148,10 +148,10 @@ "compose.published.body": "Publicado.", "compose.published.open": "Abrir", "compose.saved.body": "Publicación guardada.", - "compose_form.direct_message_warning_learn_more": "Aprender mas", + "compose_form.direct_message_warning_learn_more": "Saber más", "compose_form.encryption_warning": "Las publicaciones en Mastodon no están cifradas de extremo a extremo. No comparta ninguna información sensible en Mastodon.", - "compose_form.hashtag_warning": "Este toot no será listado bajo ningún hashtag dado que no es público. Solo toots públicos pueden ser buscados por hashtag.", - "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.", + "compose_form.hashtag_warning": "Esta publicación no será listada bajo ninguna etiqueta dado que no es pública. Solo publicaciones públicas pueden ser buscadas por etiqueta.", + "compose_form.lock_disclaimer": "Tu cuenta no está {locked}. Todos pueden seguirte para ver tus publicaciones solo para seguidores.", "compose_form.lock_disclaimer.lock": "bloqueado", "compose_form.placeholder": "¿En qué estás pensando?", "compose_form.poll.duration": "Duración de la encuesta", @@ -165,32 +165,32 @@ "compose_form.publish_form": "Publicar", "compose_form.reply": "Respuesta", "compose_form.save_changes": "Actualización", - "compose_form.spoiler.marked": "Texto oculto tras la advertencia", - "compose_form.spoiler.unmarked": "Texto no oculto", + "compose_form.spoiler.marked": "Quitar advertencia de contenido", + "compose_form.spoiler.unmarked": "Añadir advertencia de contenido", "compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)", "confirmation_modal.cancel": "Cancelar", "confirmations.block.confirm": "Bloquear", "confirmations.delete.confirm": "Eliminar", - "confirmations.delete.message": "¿Estás seguro de que quieres borrar este toot?", + "confirmations.delete.message": "¿Estás seguro de que quieres borrar esta publicación?", "confirmations.delete.title": "¿Eliminar publicación?", "confirmations.delete_list.confirm": "Eliminar", "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?", - "confirmations.delete_list.title": "¿Eliminar lista?", + "confirmations.delete_list.title": "¿Deseas eliminar la lista?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tienes cambios sin guardar en la descripción o vista previa del archivo, ¿deseas descartarlos de cualquier manera?", "confirmations.edit.confirm": "Editar", "confirmations.edit.message": "Editar sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?", - "confirmations.edit.title": "¿Sobrescribir publicación?", + "confirmations.edit.title": "¿Sobreescribir publicación?", "confirmations.logout.confirm": "Cerrar sesión", - "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?", - "confirmations.logout.title": "¿Cerrar sesión?", + "confirmations.logout.message": "¿Estás seguro de que quieres cerrar la sesión?", + "confirmations.logout.title": "¿Deseas cerrar sesión?", "confirmations.mute.confirm": "Silenciar", "confirmations.redraft.confirm": "Borrar y volver a borrador", "confirmations.redraft.message": "¿Estás seguro que quieres borrar esta publicación y editarla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán separadas.", "confirmations.redraft.title": "¿Borrar y volver a redactar la publicación?", "confirmations.reply.confirm": "Responder", "confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?", - "confirmations.reply.title": "¿Sobrescribir publicación?", + "confirmations.reply.title": "¿Sobreescribir publicación?", "confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", @@ -213,8 +213,8 @@ "dismissable_banner.dismiss": "Descartar", "dismissable_banner.explore_links": "Estas noticias están siendo discutidas por personas en este y otros servidores de la red descentralizada en este momento.", "dismissable_banner.explore_statuses": "Estas son las publicaciones que están en tendencia en la red ahora. Las publicaciones recientes con más impulsos y favoritos se muestran más arriba.", - "dismissable_banner.explore_tags": "Se trata de hashtags que están ganando adeptos en las redes sociales hoy en día. Los hashtags que son utilizados por más personas diferentes se clasifican mejor.", - "dismissable_banner.public_timeline": "Estos son los toots públicos más recientes de personas en la web social a las que sigue la gente en {domain}.", + "dismissable_banner.explore_tags": "Se trata de etiquetas que están ganando adeptos en las redes sociales hoy en día. Las etiquetas que son utilizadas por más personas diferentes se clasifican mejor.", + "dismissable_banner.public_timeline": "Estas son las publicaciones públicas más recientes de personas en la web social a las que sigue la gente en {domain}.", "domain_block_modal.block": "Bloquear servidor", "domain_block_modal.block_account_instead": "Bloquear @{name} en su lugar", "domain_block_modal.they_can_interact_with_old_posts": "Las personas de este servidor pueden interactuar con tus publicaciones antiguas.", @@ -236,7 +236,7 @@ "domain_pill.your_handle": "Tu alias:", "domain_pill.your_server": "Tu hogar digital, donde residen todas tus publicaciones. ¿No te gusta este sitio? Muévete a otro servidor en cualquier momento y llévate a tus seguidores.", "domain_pill.your_username": "Tu identificador único en este servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.", - "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.", + "embed.instructions": "Añade esta publicación a tu sitio web con el siguiente código.", "embed.preview": "Así es como se verá:", "emoji_button.activity": "Actividad", "emoji_button.clear": "Borrar", @@ -249,16 +249,16 @@ "emoji_button.objects": "Objetos", "emoji_button.people": "Gente", "emoji_button.recent": "Usados frecuentemente", - "emoji_button.search": "Buscar…", + "emoji_button.search": "Buscar...", "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", "empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información", "empty_column.account_suspended": "Cuenta suspendida", - "empty_column.account_timeline": "¡No hay toots aquí!", + "empty_column.account_timeline": "¡No hay publicaciones aquí!", "empty_column.account_unavailable": "Perfil no disponible", "empty_column.blocks": "Aún no has bloqueado a ningún usuario.", - "empty_column.bookmarked_statuses": "Aún no tienes ningún toot guardado como marcador. Cuando guardes uno, se mostrará aquí.", + "empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.", "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!", "empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.", "empty_column.domain_blocks": "Todavía no hay dominios ocultos.", @@ -266,8 +266,8 @@ "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando le des favorito a una publicación se mostrarán acá.", "empty_column.favourites": "Todavía nadie marcó como favorito esta publicación. Cuando alguien lo haga, se mostrará aquí.", "empty_column.follow_requests": "No tienes ninguna petición de seguidor. Cuando recibas una, se mostrará aquí.", - "empty_column.followed_tags": "No estás siguiendo ningún hashtag todavía. Cuando lo hagas, aparecerá aquí.", - "empty_column.hashtag": "No hay nada en este hashtag aún.", + "empty_column.followed_tags": "No estás siguiendo ninguna etiqueta todavía. Cuando lo hagas, aparecerá aquí.", + "empty_column.hashtag": "No hay nada en esta etiqueta aún.", "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.", "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.", "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.", @@ -304,7 +304,7 @@ "filter_modal.select_filter.title": "Filtrar esta publicación", "filter_modal.title.status": "Filtrar una publicación", "filter_warning.matches_filter": "Coincide con el filtro “{title}”", - "filtered_notifications_banner.pending_requests": "De {count, plural, =0 {nadie} one {una persona} other {# personas}} que puede que conozcas", + "filtered_notifications_banner.pending_requests": "De {count, plural, =0 {nadie} one {una persona} other {# people}} que puede que tú conozcas", "filtered_notifications_banner.title": "Notificaciones filtradas", "firehose.all": "Todas", "firehose.local": "Este servidor", @@ -315,7 +315,7 @@ "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", - "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", + "follow_suggestions.friends_of_friends_longer": "Popular entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los más seguidos en {domain}.", @@ -323,11 +323,11 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", - "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "Recomendamos seguir", - "followed_tags": "Hashtags seguidos", + "followed_tags": "Etiquetas seguidas", "footer.about": "Acerca de", "footer.directory": "Directorio de perfiles", "footer.get_app": "Obtener la aplicación", @@ -344,8 +344,8 @@ "hashtag.column_settings.select.no_options_message": "No se encontraron sugerencias", "hashtag.column_settings.select.placeholder": "Introducir etiquetas…", "hashtag.column_settings.tag_mode.all": "Todos estos", - "hashtag.column_settings.tag_mode.any": "Cualquiera de estos", - "hashtag.column_settings.tag_mode.none": "Ninguno de estos", + "hashtag.column_settings.tag_mode.any": "Cualquiera de estas", + "hashtag.column_settings.tag_mode.none": "Ninguna de estas", "hashtag.column_settings.tag_toggle": "Incluye etiquetas adicionales para esta columna", "hashtag.counter_by_accounts": "{count, plural, one {{counter} participante} other {{counter} participantes}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", @@ -361,7 +361,7 @@ "hints.profiles.see_more_posts": "Ver más publicaciones en {domain}", "hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.", "hints.threads.see_more": "Ver más respuestas en {domain}", - "home.column_settings.show_reblogs": "Mostrar retoots", + "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respuestas", "home.hide_announcements": "Ocultar anuncios", "home.pending_critical_update.body": "¡Por favor actualiza tu servidor Mastodon lo antes posible!", @@ -369,7 +369,7 @@ "home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!", "home.show_announcements": "Mostrar anuncios", "ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.", - "ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar", + "ignore_notifications_modal.filter_instead": "Filtrar en su lugar", "ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios", "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrar ayuda a evitar confusiones potenciales", "ignore_notifications_modal.filter_to_review_separately": "Puedes revisar las notificaciones filtradas por separado", @@ -399,13 +399,13 @@ "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}", "keyboard_shortcuts.back": "volver atrás", "keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados", - "keyboard_shortcuts.boost": "retootear", + "keyboard_shortcuts.boost": "Impulsar publicación", "keyboard_shortcuts.column": "enfocar un estado en una de las columnas", "keyboard_shortcuts.compose": "enfocar el área de texto de redacción", "keyboard_shortcuts.description": "Descripción", "keyboard_shortcuts.direct": "para abrir la columna de menciones privadas", "keyboard_shortcuts.down": "mover hacia abajo en la lista", - "keyboard_shortcuts.enter": "abrir estado", + "keyboard_shortcuts.enter": "Abrir publicación", "keyboard_shortcuts.favourite": "Marcar como favorita la publicación", "keyboard_shortcuts.favourites": "Abrir lista de favoritos", "keyboard_shortcuts.federated": "abrir el timeline federado", @@ -419,16 +419,16 @@ "keyboard_shortcuts.my_profile": "abrir tu perfil", "keyboard_shortcuts.notifications": "abrir la columna de notificaciones", "keyboard_shortcuts.open_media": "para abrir archivos multimedia", - "keyboard_shortcuts.pinned": "abrir la lista de toots destacados", + "keyboard_shortcuts.pinned": "Abrir la lista de publicaciones fijadas", "keyboard_shortcuts.profile": "abrir el perfil del autor", - "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.reply": "Responder a la publicación", "keyboard_shortcuts.requests": "abrir la lista de peticiones de seguidores", "keyboard_shortcuts.search": "para poner el foco en la búsqueda", "keyboard_shortcuts.spoilers": "para mostrar/ocultar el campo CW", "keyboard_shortcuts.start": "abrir la columna \"comenzar\"", "keyboard_shortcuts.toggle_hidden": "mostrar/ocultar texto tras aviso de contenido (CW)", "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar medios", - "keyboard_shortcuts.toot": "para comenzar un nuevo toot", + "keyboard_shortcuts.toot": "Comenzar una nueva publicación", "keyboard_shortcuts.unfocus": "para retirar el foco de la caja de redacción/búsqueda", "keyboard_shortcuts.up": "para ir hacia arriba en la lista", "lightbox.close": "Cerrar", @@ -474,7 +474,7 @@ "navigation_bar.blocks": "Usuarios bloqueados", "navigation_bar.bookmarks": "Marcadores", "navigation_bar.community_timeline": "Historia local", - "navigation_bar.compose": "Escribir un nuevo toot", + "navigation_bar.compose": "Redactar una nueva publicación", "navigation_bar.direct": "Menciones privadas", "navigation_bar.discover": "Descubrir", "navigation_bar.domain_blocks": "Dominios ocultos", @@ -482,7 +482,7 @@ "navigation_bar.favourites": "Favoritos", "navigation_bar.filters": "Palabras silenciadas", "navigation_bar.follow_requests": "Solicitudes para seguirte", - "navigation_bar.followed_tags": "Hashtags seguidos", + "navigation_bar.followed_tags": "Etiquetas seguidas", "navigation_bar.follows_and_followers": "Siguiendo y seguidores", "navigation_bar.lists": "Listas", "navigation_bar.logout": "Cerrar sesión", @@ -490,25 +490,25 @@ "navigation_bar.mutes": "Usuarios silenciados", "navigation_bar.opened_in_classic_interface": "Publicaciones, cuentas y otras páginas específicas se abren por defecto en la interfaz web clásica.", "navigation_bar.personal": "Personal", - "navigation_bar.pins": "Toots fijados", + "navigation_bar.pins": "Publicaciones fijadas", "navigation_bar.preferences": "Preferencias", "navigation_bar.public_timeline": "Historia federada", "navigation_bar.search": "Buscar", "navigation_bar.security": "Seguridad", "not_signed_in_indicator.not_signed_in": "Necesitas iniciar sesión para acceder a este recurso.", "notification.admin.report": "{name} denunció a {target}", - "notification.admin.report_account": "{name} informó de {count, plural, one {una publicación} other {# publicaciones}} de {target} por {category}", - "notification.admin.report_account_other": "{name} informó de {count, plural, one {una publicación} other {# publicaciones}} de {target}", - "notification.admin.report_statuses": "{name} informó de {target} por {category}", - "notification.admin.report_statuses_other": "{name} informó de {target}", + "notification.admin.report_account": "{name} reportó {count, plural, one {una publicación} other {# publicaciones}} de {target} por {category}", + "notification.admin.report_account_other": "{name} reportó {count, plural, one {una publicación} other {# publicaciones}} de {target}", + "notification.admin.report_statuses": "{name} reportó {target} por {category}", + "notification.admin.report_statuses_other": "{name} reportó {target}", "notification.admin.sign_up": "{name} se unio", - "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# más} other {# más}} se registraron", + "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} se registraron", "notification.favourite": "{name} marcó como favorita tu publicación", - "notification.favourite.name_and_others_with_link": "{name} y {count, plural, one {# más} other {# más}} marcaron tu publicación como favorita", + "notification.favourite.name_and_others_with_link": "{name} y {count, plural, one {# otro} other {# otros}} marcaron tu publicación como favorita", "notification.follow": "{name} te empezó a seguir", - "notification.follow.name_and_others": "{name} y {count, plural, one {# más} other {# más}} te siguieron", + "notification.follow.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} te siguieron", "notification.follow_request": "{name} ha solicitado seguirte", - "notification.follow_request.name_and_others": "{name} y {count, plural, one {# más} other {# más}} han solicitado seguirte", + "notification.follow_request.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} han solicitado seguirte", "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Respuesta privada", @@ -519,14 +519,14 @@ "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", - "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido una advertencia de moderación.", "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.", "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.", "notification.own_poll": "Tu encuesta ha terminado", - "notification.poll": "Una encuesta ha terminado", - "notification.reblog": "{name} ha retooteado tu estado", - "notification.reblog.name_and_others_with_link": "{name} y {count, plural, one {# más} other {# más}} impulsaron tu publicación", + "notification.poll": "Una encuesta en la que has votado ha terminado", + "notification.reblog": "{name} ha impulsado tu publicación", + "notification.reblog.name_and_others_with_link": "{name} y {count, plural, one {# otro} other {# otros}} impulsaron tu publicación", "notification.relationships_severance_event": "Conexiones perdidas con {name}", "notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspendido {target}, lo que significa que ya no puedes recibir actualizaciones de sus cuentas o interactuar con ellas.", "notification.relationships_severance_event.domain_block": "Un administrador de {from} ha bloqueado {target}, incluyendo {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", @@ -536,18 +536,18 @@ "notification.update": "{name} editó una publicación", "notification_requests.accept": "Aceptar", "notification_requests.accept_multiple": "{count, plural, one {Aceptar # solicitud…} other {Aceptar # solicitudes…}}", - "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Aceptar solicitud} other {Aceptar solicitudes}}", - "notification_requests.confirm_accept_multiple.message": "Vas a aceptar {count, plural, one {una solicitud} other {# solicitudes}}. ¿Quieres continuar?", - "notification_requests.confirm_accept_multiple.title": "¿Aceptar las solicitudes?", - "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Descartar solicitud} other {Descartar solicitudes}}", - "notification_requests.confirm_dismiss_multiple.message": "Vas a descartar {count, plural, one {una solicitud} other {# solicitudes}}. No podrás volver a acceder fácilmente a {count, plural, one {ella} other {ellas}} de nuevo. ¿Seguro que quieres continuar?", - "notification_requests.confirm_dismiss_multiple.title": "¿Descartar las solicitudes?", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Solicitud aceptada} other {Solicitudes aceptadas}}", + "notification_requests.confirm_accept_multiple.message": "Estás por aceptar {count, plural, one {una solicitud de notificación} other {# solicitudes de notificación}}. ¿Estás seguro de que quieres continuar?", + "notification_requests.confirm_accept_multiple.title": "¿Deseas aceptar las solicitudes de notificación?", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Solicitud descartada} other {Solicitudes descartadas}}", + "notification_requests.confirm_dismiss_multiple.message": "Estás por descartar {count, plural, one {una solicitud de notificación} other {# solicitudes de notificación}}. No serás capaz de acceder fácilmente a {count, plural, one {ella} other {ellas}} de nuevo. ¿Estás seguro de que quieres continuar?", + "notification_requests.confirm_dismiss_multiple.title": "¿Deseas descartar las solicitudes de notificación?", "notification_requests.dismiss": "Descartar", "notification_requests.dismiss_multiple": "{count, plural, one {Descartar # solicitud…} other {Descartar # solicitudes…}}", "notification_requests.edit_selection": "Editar", "notification_requests.exit_selection": "Hecho", - "notification_requests.explainer_for_limited_account": "Las notificaciones de esta cuenta han sido filtradas porque la cuenta ha sido limitada por un moderador.", - "notification_requests.explainer_for_limited_remote_account": "Las notificaciones de esta cuenta han sido filtradas porque la cuenta o su servidor ha sido limitada por un moderador.", + "notification_requests.explainer_for_limited_account": "Las notificaciones de esta cuenta han sido filtradas, ya que la cuenta ha sido limitada por un moderador.", + "notification_requests.explainer_for_limited_remote_account": "Las notificaciones de esta cuenta han sido filtradas, ya que la cuenta o su servidor ha sido limitada por un moderador.", "notification_requests.maximize": "Maximizar", "notification_requests.minimize_banner": "Minimizar banner de notificaciones filtradas", "notification_requests.notifications_from": "Notificaciones de {name}", @@ -555,7 +555,7 @@ "notification_requests.view": "Ver notificaciones", "notifications.clear": "Limpiar notificaciones", "notifications.clear_confirmation": "¿Seguro de querer borrar permanentemente todas tus notificaciones?", - "notifications.clear_title": "¿Borrar notificaciones?", + "notifications.clear_title": "¿Limpiar notificaciones?", "notifications.column_settings.admin.report": "Nuevas denuncias:", "notifications.column_settings.admin.sign_up": "Registros nuevos:", "notifications.column_settings.alert": "Notificaciones de escritorio", @@ -567,7 +567,7 @@ "notifications.column_settings.mention": "Menciones:", "notifications.column_settings.poll": "Resultados de la votación:", "notifications.column_settings.push": "Notificaciones push", - "notifications.column_settings.reblog": "Retoots:", + "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar en columna", "notifications.column_settings.sound": "Reproducir sonido", "notifications.column_settings.status": "Nuevas publicaciones:", @@ -575,7 +575,7 @@ "notifications.column_settings.unread_notifications.highlight": "Destacar notificaciones no leídas", "notifications.column_settings.update": "Ediciones:", "notifications.filter.all": "Todos", - "notifications.filter.boosts": "Retoots", + "notifications.filter.boosts": "Impulsos", "notifications.filter.favourites": "Favoritos", "notifications.filter.follows": "Seguidores", "notifications.filter.mentions": "Menciones", @@ -621,7 +621,7 @@ "onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…", "onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.", "onboarding.profile.note": "Biografía", - "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #hashtags…", + "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…", "onboarding.profile.save_and_continue": "Guardar y continuar", "onboarding.profile.title": "Configuración del perfil", "onboarding.profile.upload_avatar": "Subir foto de perfil", @@ -639,7 +639,7 @@ "onboarding.steps.publish_status.title": "Escribe tu primera publicación", "onboarding.steps.setup_profile.body": "Si rellenas tu perfil tendrás más posibilidades de que otros interactúen contigo.", "onboarding.steps.setup_profile.title": "Personaliza tu perfil", - "onboarding.steps.share_profile.body": "¡Dile a tus amigos cómo encontrarte en Mastodon!", + "onboarding.steps.share_profile.body": "Dile a tus amigos cómo encontrarte en Mastodon", "onboarding.steps.share_profile.title": "Comparte tu perfil", "onboarding.tips.2fa": "¿Sabías que? Puedes proteger tu cuenta configurando la autenticación de dos factores en la configuración de su cuenta. Funciona con cualquier aplicación TOTP de su elección, ¡no necesitas número de teléfono!", "onboarding.tips.accounts_from_other_servers": "¿Sabías que? Como Mastodon es descentralizado, algunos perfiles que encuentras están alojados en servidores distintos del tuyo. Y sin embargo, ¡puedes interactuar con ellos! ¡Su servidor corresponde a la segunda mitad de su nombre de usuario!", @@ -665,7 +665,7 @@ "privacy.private.short": "Seguidores", "privacy.public.long": "Cualquiera dentro y fuera de Mastodon", "privacy.public.short": "Público", - "privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en los hashtags, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.", + "privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en las etiquetas, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.", "privacy.unlisted.long": "Menos fanfares algorítmicos", "privacy.unlisted.short": "Público silencioso", "privacy_policy.last_updated": "Actualizado por última vez {date}", @@ -699,7 +699,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Realizado", - "report.comment.title": "¿Hay algo más que usted cree que debamos saber?", + "report.comment.title": "¿Hay algo más que creas que deberíamos saber?", "report.forward": "Reenviar a {target}", "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?", "report.mute": "Silenciar", @@ -776,9 +776,9 @@ "status.admin_status": "Abrir este estado en la interfaz de moderación", "status.block": "Bloquear a @{name}", "status.bookmark": "Añadir marcador", - "status.cancel_reblog_private": "Eliminar retoot", - "status.cannot_reblog": "Este toot no puede retootearse", - "status.continued_thread": "Continuó el hilo", + "status.cancel_reblog_private": "Deshacer impulso", + "status.cannot_reblog": "Esta publicación no puede ser impulsada", + "status.continued_thread": "Hilo continuado", "status.copy": "Copiar enlace al estado", "status.delete": "Borrar", "status.detailed_status": "Vista de conversación detallada", @@ -803,16 +803,16 @@ "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir estado", "status.pin": "Fijar", - "status.pinned": "Toot fijado", + "status.pinned": "Publicación fijada", "status.read_more": "Leer más", - "status.reblog": "Retootear", + "status.reblog": "Impulsar", "status.reblog_private": "Implusar a la audiencia original", - "status.reblogged_by": "Retooteado por {name}", + "status.reblogged_by": "Impulsado por {name}", "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", - "status.reblogs.empty": "Nadie retooteó este toot todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.reblogs.empty": "Nadie impulsó esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", - "status.replied_in_thread": "Respondió en el hilo", + "status.replied_in_thread": "Respondido en el hilo", "status.replied_to": "Respondió a {name}", "status.reply": "Responder", "status.replyAll": "Responder al hilo", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index e89256bc2f..8e33c68442 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -312,7 +312,7 @@ "follow_request.authorize": "Autoriser", "follow_request.reject": "Rejeter", "follow_requests.unlocked_explanation": "Même si votre compte n’est pas privé, l’équipe de {domain} a pensé que vous pourriez vouloir peut-être consulter manuellement les demandes d'abonnement de ces comptes.", - "follow_suggestions.curated_suggestion": "Choix du staff", + "follow_suggestions.curated_suggestion": "Sélectionné par l'équipe", "follow_suggestions.dismiss": "Ne plus afficher", "follow_suggestions.featured_longer": "Sélectionné par l'équipe de {domain}", "follow_suggestions.friends_of_friends_longer": "Populaire dans le cercle des personnes que vous suivez", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 03cb6471d6..e73ddf734f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -312,7 +312,7 @@ "follow_request.authorize": "Accepter", "follow_request.reject": "Rejeter", "follow_requests.unlocked_explanation": "Même si votre compte n’est pas privé, l’équipe de {domain} a pensé que vous pourriez vouloir consulter manuellement les demandes de suivi de ces comptes.", - "follow_suggestions.curated_suggestion": "Choix du staff", + "follow_suggestions.curated_suggestion": "Sélectionné par l'équipe", "follow_suggestions.dismiss": "Ne plus afficher", "follow_suggestions.featured_longer": "Sélectionné par l'équipe de {domain}", "follow_suggestions.friends_of_friends_longer": "Populaire dans le cercle des personnes que vous suivez", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 97748e0ee9..2b93662694 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -36,7 +36,7 @@ "account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.", "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} other {{counter} luchd-leantainn}}", "account.following": "A’ leantainn", - "account.following_counter": "{count, plural, one {Tha {counter} a’ leantainn} other {Tha {counter} a’ leantainn}}", + "account.following_counter": "{count, plural, one {A’ leantainn {counter}} other {A’ leantainn {counter}}}", "account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn neach sam bith fhathast.", "account.go_to_profile": "Tadhail air a’ phròifil", "account.hide_reblogs": "Falaich na brosnachaidhean o @{name}", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index de20d505c0..5e6ab56c23 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -787,6 +787,7 @@ "status.edit": "עריכה", "status.edited": "נערך לאחרונה {date}", "status.edited_x_times": "נערך {count, plural, one {פעם {count}} other {{count} פעמים}}", + "status.embed": "העתקת קוד להטמעה", "status.favourite": "חיבוב", "status.favourites": "{count, plural, one {חיבוב אחד} two {זוג חיבובים} other {# חיבובים}}", "status.filter": "סנן הודעה זו", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 24286a119f..822fb59e6f 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -2,12 +2,14 @@ "about.blocks": "Ulac agbur", "about.contact": "Anermis:", "about.disclaimer": "Mastodon d aseɣẓan ilelli, d aseɣẓan n uɣbalu yeldin, d tnezzut n Mastodon gGmbH.", + "about.domain_blocks.no_reason_available": "Ulac taɣẓint", "about.domain_blocks.preamble": "Maṣṭudun s umata yeḍmen-ak ad teẓreḍ agbur, ad tesdemreḍ akked yimseqdacen-nniḍen seg yal aqeddac deg fedivers. Ha-tent-an ɣur-k tsuraf i yellan deg uqeddac-agi.", "about.domain_blocks.silenced.title": "Ɣur-s talast", "about.domain_blocks.suspended.title": "Yeḥbes", "about.not_available": "Talɣut-a ur tettwabder ara deg uqeddac-a.", "about.powered_by": "Azeṭṭa inmetti yettwasɣelsen sɣur {mastodon}", "about.rules": "Ilugan n uqeddac", + "account.account_note_header": "Tamawt tudmawant", "account.add_or_remove_from_list": "Rnu neɣ kkes seg tebdarin", "account.badges.bot": "Aṛubut", "account.badges.group": "Agraw", @@ -46,6 +48,7 @@ "account.mute_notifications_short": "Susem alɣuten", "account.mute_short": "Sgugem", "account.muted": "Yettwasgugem", + "account.mutual": "Temṭafarem", "account.no_bio": "Ulac aglam i d-yettunefken.", "account.open_original_page": "Ldi asebter anasli", "account.posts": "Tisuffaɣ", @@ -62,6 +65,7 @@ "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek", "account.unfollow": "Ur ṭṭafaṛ ara", "account.unmute": "Kkes asgugem ɣef @{name}", + "account.unmute_notifications_short": "Serreḥ i yilɣa", "account.unmute_short": "Kkes asgugem", "account_note.placeholder": "Ulac iwenniten", "admin.dashboard.retention.cohort_size": "Iseqdacen imaynuten", @@ -152,6 +156,7 @@ "confirmations.edit.message": "Abeddel tura ad d-yaru izen-nni i d-tegreḍ akka tura. Tetḥeqqeḍ tebɣiḍ ad tkemmleḍ?", "confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", + "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", "confirmations.mute.confirm": "Sgugem", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", "confirmations.reply.confirm": "Err", @@ -351,6 +356,7 @@ "lists.subheading": "Tibdarin-ik·im", "load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}", "loading_indicator.label": "Yessalay-d …", + "media_gallery.hide": "Ffer-it", "mute_modal.hide_from_notifications": "Ffer-it deg ulɣuten", "mute_modal.hide_options": "Ffer tinefrunin", "mute_modal.indefinite": "Alamma ssnesreɣ asgugem fell-as", @@ -405,6 +411,7 @@ "notification.status": "{name} akken i d-yessufeɣ", "notification_requests.accept": "Qbel", "notification_requests.dismiss": "Agi", + "notification_requests.edit_selection": "Ẓreg", "notification_requests.exit_selection": "Immed", "notification_requests.notifications_from": "Alɣuten sɣur {name}", "notifications.clear": "Sfeḍ alɣuten", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 6795bc647c..cf2082e105 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -778,6 +778,7 @@ "status.bookmark": "북마크", "status.cancel_reblog_private": "부스트 취소", "status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다", + "status.continued_thread": "이어지는 글타래", "status.copy": "게시물 링크 복사", "status.delete": "삭제", "status.detailed_status": "대화 자세히 보기", @@ -786,6 +787,7 @@ "status.edit": "수정", "status.edited": "{date}에 마지막으로 편집됨", "status.edited_x_times": "{count, plural, other {{count}}} 번 수정됨", + "status.embed": "임베드 코드 받기", "status.favourite": "좋아요", "status.favourites": "{count, plural, other {좋아요}}", "status.filter": "이 게시물을 필터", @@ -810,6 +812,7 @@ "status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.", "status.redraft": "지우고 다시 쓰기", "status.remove_bookmark": "북마크 삭제", + "status.replied_in_thread": "글타래에 답장", "status.replied_to": "{name} 님에게", "status.reply": "답장", "status.replyAll": "글타래에 답장", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index b53f65ab43..53b3c0fcaa 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -409,6 +409,7 @@ "lists.subheading": "Tavi saraksti", "load_pending": "{count, plural, one {# jauna lieta} other {# jaunas lietas}}", "loading_indicator.label": "Ielādē…", + "media_gallery.hide": "Paslēpt", "moved_to_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots, jo Tu pārcēlies uz kontu {movedToAccount}.", "mute_modal.hide_from_notifications": "Paslēpt paziņojumos", "mute_modal.hide_options": "Paslēpt iespējas", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 4a03f2b6e8..d97b1658c5 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -34,7 +34,9 @@ "account.follow_back": "Подписаться в ответ", "account.followers": "Подписчики", "account.followers.empty": "На этого пользователя пока никто не подписан.", + "account.followers_counter": "{count, plural, one {{counter} последователя} other {{counter} последователей}}", "account.following": "Подписки", + "account.following_counter": "{count, plural, one {{counter} последующий} other {{counter} последующие}}", "account.follows.empty": "Этот пользователь пока ни на кого не подписался.", "account.go_to_profile": "Перейти к профилю", "account.hide_reblogs": "Скрыть продвижения от @{name}", @@ -48,7 +50,7 @@ "account.moved_to": "У {name} теперь новый аккаунт:", "account.mute": "Игнорировать @{name}", "account.mute_notifications_short": "Отключить уведомления", - "account.mute_short": "Глохни!", + "account.mute_short": "Приглушить", "account.muted": "Игнорируется", "account.mutual": "Взаимно", "account.no_bio": "Описание не предоставлено.", @@ -94,6 +96,8 @@ "block_modal.title": "Заблокировать пользователя?", "block_modal.you_wont_see_mentions": "Вы не увидите записи, которые упоминают его.", "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз", + "boost_modal.reblog": "Повысить пост?", + "boost_modal.undo_reblog": "Разгрузить пост?", "bundle_column_error.copy_stacktrace": "Скопировать отчет об ошибке", "bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.", "bundle_column_error.error.title": "О нет!", @@ -298,6 +302,8 @@ "filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую", "filter_modal.select_filter.title": "Фильтровать этот пост", "filter_modal.title.status": "Фильтровать пост", + "filter_warning.matches_filter": "Соответствует фильтру \"{title}\"", + "filtered_notifications_banner.pending_requests": "Вы можете знать {count, plural, =0 {ни один} one {один человек} other {# люди}}", "filtered_notifications_banner.title": "Отфильтрованные уведомления", "firehose.all": "Все", "firehose.local": "Текущий сервер", @@ -346,6 +352,14 @@ "hashtag.follow": "Подписаться на новые посты", "hashtag.unfollow": "Отписаться", "hashtags.and_other": "...и {count, plural, other {# ещё}}", + "hints.profiles.followers_may_be_missing": "Последователи для этого профиля могут отсутствовать.", + "hints.profiles.follows_may_be_missing": "Фолловеры для этого профиля могут отсутствовать.", + "hints.profiles.posts_may_be_missing": "Некоторые сообщения из этого профиля могут отсутствовать.", + "hints.profiles.see_more_followers": "Посмотреть больше подписчиков на {domain}", + "hints.profiles.see_more_follows": "Смотрите другие материалы по теме {domain}", + "hints.profiles.see_more_posts": "Посмотреть другие сообщения на {domain}", + "hints.threads.replies_may_be_missing": "Ответы с других серверов могут отсутствовать.", + "hints.threads.see_more": "Посмотреть другие ответы на {domain}", "home.column_settings.show_reblogs": "Показывать продвижения", "home.column_settings.show_replies": "Показывать ответы", "home.hide_announcements": "Скрыть объявления", @@ -353,7 +367,17 @@ "home.pending_critical_update.link": "Посмотреть обновления", "home.pending_critical_update.title": "Доступно критическое обновление безопасности!", "home.show_announcements": "Показать объявления", + "ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы проигнорировали их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.", + "ignore_notifications_modal.filter_instead": "Фильтр вместо", "ignore_notifications_modal.filter_to_act_users": "Вы и далее сможете принять, отвергнуть и жаловаться на пользователей", + "ignore_notifications_modal.filter_to_avoid_confusion": "Фильтрация помогает избежать потенциальной путаницы", + "ignore_notifications_modal.filter_to_review_separately": "Вы можете просматривать отфильтрованные уведомления отдельно", + "ignore_notifications_modal.ignore": "Игнорировать уведомления", + "ignore_notifications_modal.limited_accounts_title": "Игнорировать уведомления от модерируемых аккаунтов?", + "ignore_notifications_modal.new_accounts_title": "Игнорировать уведомления от новых аккаунтов?", + "ignore_notifications_modal.not_followers_title": "Игнорировать уведомления от людей, которые не следят за вами?", + "ignore_notifications_modal.not_following_title": "Игнорировать уведомления от людей, за которыми вы не следите?", + "ignore_notifications_modal.private_mentions_title": "Игнорировать уведомления о нежелательных личных сообщениях?", "interaction_modal.description.favourite": "С учётной записью Mastodon, вы можете добавить этот пост в избранное, чтобы сохранить его на будущее и дать автору знать, что пост вам понравился.", "interaction_modal.description.follow": "С учётной записью Mastodon вы можете подписаться на {name}, чтобы получать их посты в своей домашней ленте.", "interaction_modal.description.reblog": "С учётной записью Mastodon, вы можете продвинуть этот пост, чтобы поделиться им со своими подписчиками.", @@ -432,6 +456,7 @@ "lists.subheading": "Ваши списки", "load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}", "loading_indicator.label": "Загрузка…", + "media_gallery.hide": "Скрыть", "moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.", "mute_modal.hide_from_notifications": "Скрыть из уведомлений", "mute_modal.hide_options": "Скрыть параметры", @@ -443,6 +468,7 @@ "mute_modal.you_wont_see_mentions": "Вы не увидите постов, которые их упоминают.", "mute_modal.you_wont_see_posts": "Они по-прежнему смогут видеть ваши посты, но вы не сможете видеть их посты.", "navigation_bar.about": "О проекте", + "navigation_bar.administration": "Администрация", "navigation_bar.advanced_interface": "Включить многоколоночный интерфейс", "navigation_bar.blocks": "Заблокированные пользователи", "navigation_bar.bookmarks": "Закладки", @@ -459,6 +485,7 @@ "navigation_bar.follows_and_followers": "Подписки и подписчики", "navigation_bar.lists": "Списки", "navigation_bar.logout": "Выйти", + "navigation_bar.moderation": "Модерация", "navigation_bar.mutes": "Игнорируемые пользователи", "navigation_bar.opened_in_classic_interface": "Сообщения, учётные записи и другие специфические страницы по умолчанию открываются в классическом веб-интерфейсе.", "navigation_bar.personal": "Личное", @@ -469,10 +496,22 @@ "navigation_bar.security": "Безопасность", "not_signed_in_indicator.not_signed_in": "Вам нужно войти, чтобы иметь доступ к этому ресурсу.", "notification.admin.report": "{name} сообщил о {target}", + "notification.admin.report_account": "{name} сообщил {count, plural, one {один пост} other {# постов}} от {target} для {category}", + "notification.admin.report_account_other": "{name} сообщил {count, plural, one {одно сообщение} other {# сообщений}} от {target}", + "notification.admin.report_statuses": "{name} сообщил {target} для {category}", + "notification.admin.report_statuses_other": "{name} сообщает {target}", "notification.admin.sign_up": "{name} зарегистрирован", + "notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# другой} other {# другие}} подписались", "notification.favourite": "{name} добавил(а) ваш пост в избранное", + "notification.favourite.name_and_others_with_link": "{name} и {count, plural, one {# другие} other {# другие}} отдали предпочтение вашему посту", "notification.follow": "{name} подписался (-лась) на вас", "notification.follow_request": "{name} отправил запрос на подписку", + "notification.follow_request.name_and_others": "{name} и {count, plural, one {# другие} other {# другие}} последовали за тобой", + "notification.label.mention": "Упоминание", + "notification.label.private_mention": "Частное упоминание", + "notification.label.private_reply": "Частный ответ", + "notification.label.reply": "Ответить", + "notification.mention": "Упоминание", "notification.moderation-warning.learn_more": "Узнать больше", "notification.moderation_warning": "Вы получили предупреждение от модерации", "notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.", @@ -483,7 +522,9 @@ "notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.", "notification.moderation_warning.action_suspend": "Действие вашей учётной записи приостановлено.", "notification.own_poll": "Ваш опрос закончился", + "notification.poll": "Голосование, в котором вы приняли участие, завершилось", "notification.reblog": "{name} продвинул(а) ваш пост", + "notification.reblog.name_and_others_with_link": "{name} и {count, plural, one {# other} other {# others}} увеличили ваш пост", "notification.relationships_severance_event": "Потеряно соединение с {name}", "notification.relationships_severance_event.account_suspension": "Администратор {from} заблокировал {target}, что означает, что вы больше не сможете получать обновления от них или взаймодествовать с ними.", "notification.relationships_severance_event.domain_block": "Администратор {from} заблокировал {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.", @@ -492,10 +533,19 @@ "notification.status": "{name} только что запостил", "notification.update": "{name} изменил(а) пост", "notification_requests.accept": "Принять", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Принять запрос} other {Принять запросы}}", + "notification_requests.confirm_accept_multiple.title": "Принимать запросы на уведомления?", + "notification_requests.confirm_dismiss_multiple.title": "Отклонять запросы на уведомления?", "notification_requests.dismiss": "Отклонить", + "notification_requests.edit_selection": "Редактировать", + "notification_requests.exit_selection": "Готово", + "notification_requests.explainer_for_limited_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись была ограничена модератором.", + "notification_requests.explainer_for_limited_remote_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись или ее сервер были ограничены модератором.", "notification_requests.maximize": "Развернуть", + "notification_requests.minimize_banner": "Минимизация баннера отфильтрованных уведомлений", "notification_requests.notifications_from": "Уведомления от {name}", "notification_requests.title": "Отфильтрованные уведомления", + "notification_requests.view": "Просмотр уведомлений", "notifications.clear": "Очистить уведомления", "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?", "notifications.clear_title": "Сбросить уведомления?", @@ -530,7 +580,14 @@ "notifications.permission_denied": "Уведомления на рабочем столе недоступны, так как вы запретили их отправку в браузере. Проверьте настройки для сайта, чтобы включить их обратно.", "notifications.permission_denied_alert": "Уведомления на рабочем столе недоступны, так как вы ранее отклонили запрос на их отправку.", "notifications.permission_required": "Чтобы включить уведомления на рабочем столе, необходимо разрешить их в браузере.", + "notifications.policy.accept": "Принять", + "notifications.policy.accept_hint": "Показать в уведомлениях", "notifications.policy.drop": "Игнорируем", + "notifications.policy.drop_hint": "Отправить в пустоту, чтобы никогда больше не увидеть", + "notifications.policy.filter": "Фильтр", + "notifications.policy.filter_hint": "Отправка в папку фильтрованных уведомлений", + "notifications.policy.filter_limited_accounts_hint": "Ограничено модераторами сервера", + "notifications.policy.filter_limited_accounts_title": "Модерируемые аккаунты", "notifications.policy.filter_new_accounts.hint": "Создано в течение последних {days, plural, one {один день} few {# дней} many {# дней} other {# дня}}", "notifications.policy.filter_new_accounts_title": "Новые учётные записи", "notifications.policy.filter_not_followers_title": "Люди, не подписанные на вас", @@ -538,6 +595,7 @@ "notifications.policy.filter_not_following_title": "Люди, на которых вы не подписаны", "notifications.policy.filter_private_mentions_hint": "Фильтруется, если только это не ответ на ваше собственное упоминание или если вы подписаны на отправителя", "notifications.policy.filter_private_mentions_title": "Нежелательные личные упоминания", + "notifications.policy.title": "………Управлять уведомлениями от…", "notifications_permission_banner.enable": "Включить уведомления", "notifications_permission_banner.how_to_control": "Получайте уведомления даже когда Mastodon закрыт, включив уведомления на рабочем столе. А чтобы лишний шум не отвлекал, вы можете настроить какие уведомления вы хотите получать, нажав на кнопку {icon} выше.", "notifications_permission_banner.title": "Будьте в курсе происходящего", @@ -666,6 +724,7 @@ "report_notification.categories.legal": "Правовая информация", "report_notification.categories.legal_sentence": "срамной контент", "report_notification.categories.other": "Прочее", + "report_notification.categories.other_sentence": "другое", "report_notification.categories.spam": "Спам", "report_notification.categories.spam_sentence": "спам", "report_notification.categories.violation": "Нарушение правил", @@ -696,8 +755,11 @@ "server_banner.about_active_users": "Люди, заходившие на этот сервер за последние 30 дней (ежемесячные активные пользователи)", "server_banner.active_users": "активные пользователи", "server_banner.administered_by": "Управляется:", + "server_banner.is_one_of_many": "{domain} - это один из многих независимых серверов Mastodon, которые вы можете использовать для участия в fediverse.", "server_banner.server_stats": "Статистика сервера:", "sign_in_banner.create_account": "Создать учётную запись", + "sign_in_banner.follow_anyone": "Следите за любым человеком в федеральной вселенной и смотрите все в хронологическом порядке. Никаких алгоритмов, рекламы или клик бейта.", + "sign_in_banner.mastodon_is": "Mastodon - лучший способ быть в курсе всего происходящего.", "sign_in_banner.sign_in": "Войти", "sign_in_banner.sso_redirect": "Войдите или Зарегистрируйтесь", "status.admin_account": "Открыть интерфейс модератора для @{name}", @@ -707,6 +769,7 @@ "status.bookmark": "Сохранить в закладки", "status.cancel_reblog_private": "Не продвигать", "status.cannot_reblog": "Этот пост не может быть продвинут", + "status.continued_thread": "Продолжение темы", "status.copy": "Скопировать ссылку на пост", "status.delete": "Удалить", "status.detailed_status": "Подробный просмотр обсуждения", @@ -715,6 +778,7 @@ "status.edit": "Изменить", "status.edited": "Дата последнего изменения: {date}", "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}", + "status.embed": "Получить код для встраивания", "status.favourite": "Избранное", "status.filter": "Фильтровать этот пост", "status.history.created": "{name} создал {date}", @@ -737,6 +801,7 @@ "status.reblogs.empty": "Никто ещё не продвинул этот пост. Как только кто-то это сделает, они появятся здесь.", "status.redraft": "Создать заново", "status.remove_bookmark": "Убрать из закладок", + "status.replied_in_thread": "Ответил в теме", "status.replied_to": "Ответил(а) {name}", "status.reply": "Ответить", "status.replyAll": "Ответить всем", diff --git a/app/javascript/mastodon/scroll.ts b/app/javascript/mastodon/scroll.ts index 35e13a4527..0756edb4ce 100644 --- a/app/javascript/mastodon/scroll.ts +++ b/app/javascript/mastodon/scroll.ts @@ -38,13 +38,20 @@ const scroll = ( const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style; -export const scrollRight = (node: Element, position: number) => { - if (isScrollBehaviorSupported) - node.scrollTo({ left: position, behavior: 'smooth' }); - else scroll(node, 'scrollLeft', position); -}; +export const scrollRight = (node: Element, position: number) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ left: position, behavior: 'smooth' }); + } else { + scroll(node, 'scrollLeft', position); + } + }); -export const scrollTop = (node: Element) => { - if (isScrollBehaviorSupported) node.scrollTo({ top: 0, behavior: 'smooth' }); - else scroll(node, 'scrollTop', 0); -}; +export const scrollTop = (node: Element) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + scroll(node, 'scrollTop', 0); + } + }); diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 40d69136a8..59b2fd7582 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -209,7 +209,6 @@ const KNOWN_EVENT_TYPES = [ 'notification', 'conversation', 'filters_changed', - 'encrypted_message', 'announcement', 'announcement.delete', 'announcement.reaction', diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx index f405090730..8a6f5a3377 100644 --- a/app/javascript/mastodon/test_helpers.tsx +++ b/app/javascript/mastodon/test_helpers.tsx @@ -8,6 +8,14 @@ import { render as rtlRender } from '@testing-library/react'; import { IdentityContext } from './identity_context'; +beforeEach(() => { + global.requestIdleCallback = jest + .fn() + .mockImplementation((fn: () => void) => { + fn(); + }); +}); + function render( ui: React.ReactElement, { diff --git a/app/javascript/material-icons/400-24px/fit_screen-fill.svg b/app/javascript/material-icons/400-24px/fit_screen-fill.svg new file mode 100644 index 0000000000..a2ed8ca581 --- /dev/null +++ b/app/javascript/material-icons/400-24px/fit_screen-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fit_screen.svg b/app/javascript/material-icons/400-24px/fit_screen.svg new file mode 100644 index 0000000000..d8d06f6e8b --- /dev/null +++ b/app/javascript/material-icons/400-24px/fit_screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 76ede26233..76bdc4022e 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -1,3 +1,5 @@ +@use 'sass:color'; + // Dependent colors $black: #000000; $white: #ffffff; @@ -47,11 +49,19 @@ $account-background-color: $white !default; // Invert darkened and lightened colors @function darken($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) + $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) + $amount + ); } @function lighten($color, $amount) { - @return hsl(hue($color), saturation($color), lightness($color) - $amount); + @return hsl( + hue($color), + color.channel($color, 'saturation', $space: hsl), + color.channel($color, 'lightness', $space: hsl) - $amount + ); } $emojis-requiring-inversion: 'chains'; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cde77fbfc1..a6dc888e57 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2032,13 +2032,14 @@ body > [data-popper-placement] { display: block; position: relative; border-radius: var(--avatar-border-radius); + background-color: var(--surface-background-color); img { - display: block; width: 100%; height: 100%; object-fit: cover; border-radius: var(--avatar-border-radius); + display: inline-block; // to not show broken images } &-inline { @@ -5763,19 +5764,34 @@ a.status-card { height: 100%; position: relative; - &__close, - &__zoom-button { - color: rgba($white, 0.7); + &__buttons { + position: absolute; + inset-inline-end: 8px; + top: 8px; + z-index: 100; + display: flex; + gap: 8px; + align-items: center; - &:hover, - &:focus, - &:active { - color: $white; - background-color: rgba($white, 0.15); - } + .icon-button { + color: rgba($white, 0.7); + padding: 8px; - &:focus { - background-color: rgba($white, 0.3); + .icon { + width: 24px; + height: 24px; + } + + &:hover, + &:focus, + &:active { + color: $white; + background-color: rgba($white, 0.15); + } + + &:focus { + background-color: rgba($white, 0.3); + } } } } @@ -5936,28 +5952,6 @@ a.status-card { } } -.media-modal__close { - position: absolute; - inset-inline-end: 8px; - top: 8px; - z-index: 100; -} - -.media-modal__zoom-button { - position: absolute; - inset-inline-end: 64px; - top: 8px; - z-index: 100; - pointer-events: auto; - transition: opacity 0.3s linear; - will-change: opacity; -} - -.media-modal__zoom-button--hidden { - pointer-events: none; - opacity: 0; -} - .onboarding-modal, .error-modal, .embed-modal { diff --git a/app/javascript/svg-icons/actual_size.svg b/app/javascript/svg-icons/actual_size.svg new file mode 100644 index 0000000000..75939cac87 --- /dev/null +++ b/app/javascript/svg-icons/actual_size.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f69b68392a..9b55b92840 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -8,44 +8,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity dereference_object! - case @object['type'] - when 'EncryptedMessage' - create_encrypted_message - else - create_status - end + create_status end private - def create_encrypted_message - return reject_payload! if non_matching_uri_hosts?(@account.uri, object_uri) || @options[:delivered_to_account_id].blank? - - target_account = Account.find(@options[:delivered_to_account_id]) - target_device = target_account.devices.find_by(device_id: @object.dig('to', 'deviceId')) - - return if target_device.nil? - - target_device.encrypted_messages.create!( - from_account: @account, - from_device_id: @object.dig('attributedTo', 'deviceId'), - type: @object['messageType'], - body: @object['cipherText'], - digest: @object.dig('digest', 'digestValue'), - message_franking: message_franking.to_token - ) - end - - def message_franking - MessageFranking.new( - hmac: @object.dig('digest', 'digestValue'), - original_franking: @object['messageFranking'], - source_account_id: @account.id, - target_account_id: @options[:delivered_to_account_id], - timestamp: Time.now.utc - ) - end - def create_status return reject_payload! if unsupported_object_type? || non_matching_uri_hosts?(@account.uri, object_uri) || tombstone_exists? || !related_to_local_activity? diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 5a1710c52d..3c278b37d8 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -618,7 +618,7 @@ class FeedManager arr = crutches[:active_mentions][s.id] || [] arr.push(s.account_id) - if s.reblog? + if s.reblog? && s.reblog.present? arr.push(s.reblog.account_id) arr.concat(crutches[:active_mentions][s.reblog_of_id] || []) end diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb index 0aebb13fce..af967ac215 100644 --- a/app/lib/inline_renderer.rb +++ b/app/lib/inline_renderer.rb @@ -20,8 +20,6 @@ class InlineRenderer serializer = REST::AnnouncementSerializer when :reaction serializer = REST::ReactionSerializer - when :encrypted_message - serializer = REST::EncryptedMessageSerializer else return end diff --git a/app/lib/request.rb b/app/lib/request.rb index ab42e82300..d7da9fe63c 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -77,7 +77,7 @@ class Request @url = Addressable::URI.parse(url).normalize @http_client = options.delete(:http_client) @allow_local = options.delete(:allow_local) - @full_path = options.delete(:with_query_string) + @full_path = !options.delete(:omit_query_string) @options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket) @options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT) @options = @options.merge(proxy_url) if use_proxy? diff --git a/app/lib/vacuum/system_keys_vacuum.rb b/app/lib/vacuum/system_keys_vacuum.rb deleted file mode 100644 index ceee2fd164..0000000000 --- a/app/lib/vacuum/system_keys_vacuum.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -class Vacuum::SystemKeysVacuum - def perform - vacuum_expired_system_keys! - end - - private - - def vacuum_expired_system_keys! - SystemKey.expired.delete_all - end -end diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index 8dd7b6e59f..72a2c2e64e 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -56,9 +56,11 @@ class AdminMailer < ApplicationMailer def new_critical_software_updates @software_updates = SoftwareUpdate.where(urgent: true).to_a.sort_by(&:gem_version) - headers['Priority'] = 'urgent' - headers['X-Priority'] = '1' - headers['Importance'] = 'high' + headers( + 'Importance' => 'high', + 'Priority' => 'urgent', + 'X-Priority' => '1' + ) locale_for_account(@me) do mail subject: default_i18n_subject(instance: @instance) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 35f0b5fee1..9a209aa77b 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -16,8 +16,10 @@ class ApplicationMailer < ActionMailer::Base end def set_autoreply_headers! - headers['Precedence'] = 'list' - headers['X-Auto-Response-Suppress'] = 'All' - headers['Auto-Submitted'] = 'auto-generated' + headers( + 'Auto-Submitted' => 'auto-generated', + 'Precedence' => 'list', + 'X-Auto-Response-Suppress' => 'All' + ) end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 4eb38ec340..6b21b4bedd 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -6,7 +6,10 @@ class NotificationMailer < ApplicationMailer :routing before_action :process_params - before_action :set_status, only: [:mention, :favourite, :reblog] + with_options only: %i(mention favourite reblog) do + before_action :set_status + after_action :thread_by_conversation! + end before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request] after_action :set_list_headers! @@ -18,7 +21,6 @@ class NotificationMailer < ApplicationMailer return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @status.account.acct) end end @@ -35,7 +37,6 @@ class NotificationMailer < ApplicationMailer return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end end @@ -44,7 +45,6 @@ class NotificationMailer < ApplicationMailer return unless @user.functional? && @status.present? locale_for_account(@me) do - thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end end @@ -76,17 +76,21 @@ class NotificationMailer < ApplicationMailer end def set_list_headers! - headers['List-ID'] = "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>" - headers['List-Unsubscribe'] = "<#{@unsubscribe_url}>" - headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click' + headers( + 'List-ID' => "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>", + 'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click', + 'List-Unsubscribe' => "<#{@unsubscribe_url}>" + ) end - def thread_by_conversation(conversation) - return if conversation.nil? + def thread_by_conversation! + return if @status.conversation.nil? - msg_id = "" + conversation_message_id = "" - headers['In-Reply-To'] = msg_id - headers['References'] = msg_id + headers( + 'In-Reply-To' => conversation_message_id, + 'References' => conversation_message_id + ) end end diff --git a/app/models/account.rb b/app/models/account.rb index cda8dfb393..a9c9d91c89 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -44,7 +44,6 @@ # hide_collections :boolean # avatar_storage_schema_version :integer # header_storage_schema_version :integer -# devices_url :string # suspension_origin :integer # sensitized_at :datetime # trendable :boolean @@ -56,11 +55,12 @@ class Account < ApplicationRecord self.ignored_columns += %w( - subscription_expires_at - secret + devices_url + hub_url remote_url salmon_url - hub_url + secret + subscription_expires_at trust_level ) diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 1c67b07e51..637e785953 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -7,9 +7,6 @@ module Account::Associations # Local users has_one :user, inverse_of: :account, dependent: :destroy - # E2EE - has_many :devices, dependent: :destroy, inverse_of: :account - # Timelines has_many :statuses, inverse_of: :account, dependent: :destroy has_many :favourites, inverse_of: :account, dependent: :destroy diff --git a/app/models/concerns/reviewable.rb b/app/models/concerns/reviewable.rb index 1f70474b35..d08b473342 100644 --- a/app/models/concerns/reviewable.rb +++ b/app/models/concerns/reviewable.rb @@ -3,6 +3,11 @@ module Reviewable extend ActiveSupport::Concern + included do + scope :reviewed, -> { where.not(reviewed_at: nil) } + scope :unreviewed, -> { where(reviewed_at: nil) } + end + def requires_review? reviewed_at.nil? end diff --git a/app/models/device.rb b/app/models/device.rb deleted file mode 100644 index 5dc6cf1e66..0000000000 --- a/app/models/device.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: devices -# -# id :bigint(8) not null, primary key -# access_token_id :bigint(8) -# account_id :bigint(8) -# device_id :string default(""), not null -# name :string default(""), not null -# fingerprint_key :text default(""), not null -# identity_key :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class Device < ApplicationRecord - belongs_to :access_token, class_name: 'Doorkeeper::AccessToken' - belongs_to :account - - has_many :one_time_keys, dependent: :destroy, inverse_of: :device - has_many :encrypted_messages, dependent: :destroy, inverse_of: :device - - validates :name, :fingerprint_key, :identity_key, presence: true - validates :fingerprint_key, :identity_key, ed25519_key: true - - before_save :invalidate_associations, if: -> { device_id_changed? || fingerprint_key_changed? || identity_key_changed? } - - private - - def invalidate_associations - one_time_keys.destroy_all - encrypted_messages.destroy_all - end -end diff --git a/app/models/encrypted_message.rb b/app/models/encrypted_message.rb deleted file mode 100644 index 3e7e95594c..0000000000 --- a/app/models/encrypted_message.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: encrypted_messages -# -# id :bigint(8) not null, primary key -# device_id :bigint(8) -# from_account_id :bigint(8) -# from_device_id :string default(""), not null -# type :integer default(0), not null -# body :text default(""), not null -# digest :text default(""), not null -# message_franking :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class EncryptedMessage < ApplicationRecord - self.inheritance_column = nil - - include Paginable - include Redisable - - scope :up_to, ->(id) { where(arel_table[:id].lteq(id)) } - - belongs_to :device - belongs_to :from_account, class_name: 'Account' - - around_create Mastodon::Snowflake::Callbacks - - after_commit :push_to_streaming_api - - private - - def push_to_streaming_api - return if destroyed? || !subscribed_to_timeline? - - PushEncryptedMessageWorker.perform_async(id) - end - - def subscribed_to_timeline? - redis.exists?("subscribed:#{streaming_channel}") - end - - def streaming_channel - "timeline:#{device.account_id}:#{device.device_id}" - end -end diff --git a/app/models/message_franking.rb b/app/models/message_franking.rb deleted file mode 100644 index c72bd1ccac..0000000000 --- a/app/models/message_franking.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class MessageFranking - attr_reader :hmac, :source_account_id, :target_account_id, - :timestamp, :original_franking - - def initialize(attributes = {}) - @hmac = attributes[:hmac] - @source_account_id = attributes[:source_account_id] - @target_account_id = attributes[:target_account_id] - @timestamp = attributes[:timestamp] - @original_franking = attributes[:original_franking] - end - - def to_token - crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj) - crypt.encrypt_and_sign(self) - end -end diff --git a/app/models/one_time_key.rb b/app/models/one_time_key.rb deleted file mode 100644 index 23604e2f7d..0000000000 --- a/app/models/one_time_key.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: one_time_keys -# -# id :bigint(8) not null, primary key -# device_id :bigint(8) -# key_id :string default(""), not null -# key :text default(""), not null -# signature :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# - -class OneTimeKey < ApplicationRecord - belongs_to :device - - validates :key_id, :key, :signature, presence: true - validates :key, ed25519_key: true - validates :signature, ed25519_signature: { message: :key, verify_key: ->(one_time_key) { one_time_key.device.fingerprint_key } } -end diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 48944fe638..889176036c 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -34,8 +34,6 @@ class PreviewCardProvider < ApplicationRecord scope :trendable, -> { where(trendable: true) } scope :not_trendable, -> { where(trendable: false) } - scope :reviewed, -> { where.not(reviewed_at: nil) } - scope :pending_review, -> { where(reviewed_at: nil) } def self.matching_domain(domain) segments = domain.split('.') diff --git a/app/models/system_key.rb b/app/models/system_key.rb deleted file mode 100644 index 1be399dd68..0000000000 --- a/app/models/system_key.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: system_keys -# -# id :bigint(8) not null, primary key -# key :binary -# created_at :datetime not null -# updated_at :datetime not null -# -class SystemKey < ApplicationRecord - ROTATION_PERIOD = 1.week.freeze - - before_validation :set_key - - scope :expired, ->(now = Time.now.utc) { where(arel_table[:created_at].lt(now - (ROTATION_PERIOD * 3))) } - - class << self - def current_key - previous_key = order(id: :asc).last - - if previous_key && previous_key.created_at >= ROTATION_PERIOD.ago - previous_key.key - else - create.key - end - end - end - - private - - def set_key - return if key.present? - - cipher = OpenSSL::Cipher.new('AES-256-GCM') - cipher.encrypt - - self.key = cipher.random_key - end -end diff --git a/app/models/tag.rb b/app/models/tag.rb index acf514919b..93210eb307 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -50,8 +50,6 @@ class Tag < ApplicationRecord validate :validate_name_change, if: -> { !new_record? && name_changed? } validate :validate_display_name_change, if: -> { !new_record? && display_name_changed? } - scope :reviewed, -> { where.not(reviewed_at: nil) } - scope :unreviewed, -> { where(reviewed_at: nil) } scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) } scope :usable, -> { where(usable: [true, nil]) } scope :not_usable, -> { where(usable: false) } @@ -127,7 +125,7 @@ class Tag < ApplicationRecord query = Tag.matches_name(stripped_term) query = query.merge(Tag.listable) if options[:exclude_unlistable] - query = query.merge(matching_name(stripped_term).or(where.not(reviewed_at: nil))) if options[:exclude_unreviewed] + query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed] query.order(Arel.sql('length(name) ASC, name ASC')) .limit(limit) diff --git a/app/models/trends/preview_card_provider_filter.rb b/app/models/trends/preview_card_provider_filter.rb index 219793f01e..33f4e97912 100644 --- a/app/models/trends/preview_card_provider_filter.rb +++ b/app/models/trends/preview_card_provider_filter.rb @@ -41,7 +41,7 @@ class Trends::PreviewCardProviderFilter when 'rejected' PreviewCardProvider.not_trendable when 'pending_review' - PreviewCardProvider.pending_review + PreviewCardProvider.unreviewed else raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end diff --git a/app/presenters/activitypub/activity_presenter.rb b/app/presenters/activitypub/activity_presenter.rb index 38e8527e8e..994cbeaf48 100644 --- a/app/presenters/activitypub/activity_presenter.rb +++ b/app/presenters/activitypub/activity_presenter.rb @@ -26,16 +26,5 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model end end end - - def from_encrypted_message(encrypted_message) - new.tap do |presenter| - presenter.id = ActivityPub::TagManager.instance.generate_uri_for(nil) - presenter.type = 'Create' - presenter.actor = ActivityPub::TagManager.instance.uri_for(encrypted_message.source_account) - presenter.published = Time.now.utc - presenter.to = ActivityPub::TagManager.instance.uri_for(encrypted_message.target_account) - presenter.virtual_object = encrypted_message - end - end end end diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb index 5bdf53f032..23a5b42bc7 100644 --- a/app/serializers/activitypub/activity_serializer.rb +++ b/app/serializers/activitypub/activity_serializer.rb @@ -5,8 +5,6 @@ class ActivityPub::ActivitySerializer < ActivityPub::Serializer case model.class.name when 'Status' ActivityPub::NoteSerializer - when 'DeliverToDeviceService::EncryptedMessage' - ActivityPub::EncryptedMessageSerializer else super end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index a6281e23b9..f698e758e8 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -7,7 +7,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer context :security context_extensions :manually_approves_followers, :featured, :also_known_as, - :moved_to, :property_value, :discoverable, :olm, :suspended, + :moved_to, :property_value, :discoverable, :suspended, :memorial, :indexable, :attribution_domains attributes :id, :type, :following, :followers, @@ -21,7 +21,6 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer has_many :virtual_tags, key: :tag has_many :virtual_attachments, key: :attachment - attribute :devices, unless: :instance_actor? attribute :moved_to, if: :moved? attribute :also_known_as, if: :also_known_as? attribute :suspended, if: :suspended? @@ -72,10 +71,6 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object) end - def devices - account_collection_url(object, :devices) - end - def outbox object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object) end diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index 34026a6b5b..1b410cecae 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -14,8 +14,6 @@ class ActivityPub::CollectionSerializer < ActivityPub::Serializer case model.class.name when 'Status' ActivityPub::NoteSerializer - when 'Device' - ActivityPub::DeviceSerializer when 'FeaturedTag' ActivityPub::HashtagSerializer when 'ActivityPub::CollectionPresenter' diff --git a/app/serializers/activitypub/device_serializer.rb b/app/serializers/activitypub/device_serializer.rb deleted file mode 100644 index 5f0fdc8af9..0000000000 --- a/app/serializers/activitypub/device_serializer.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::DeviceSerializer < ActivityPub::Serializer - context_extensions :olm - - include RoutingHelper - - class FingerprintKeySerializer < ActivityPub::Serializer - attributes :type, :public_key_base64 - - def type - 'Ed25519Key' - end - - def public_key_base64 - object.fingerprint_key - end - end - - class IdentityKeySerializer < ActivityPub::Serializer - attributes :type, :public_key_base64 - - def type - 'Curve25519Key' - end - - def public_key_base64 - object.identity_key - end - end - - attributes :device_id, :type, :name, :claim - - has_one :fingerprint_key, serializer: FingerprintKeySerializer - has_one :identity_key, serializer: IdentityKeySerializer - - def type - 'Device' - end - - def claim - account_claim_url(object.account, id: object.device_id) - end - - def fingerprint_key - object - end - - def identity_key - object - end -end diff --git a/app/serializers/activitypub/encrypted_message_serializer.rb b/app/serializers/activitypub/encrypted_message_serializer.rb deleted file mode 100644 index 3c525d23e5..0000000000 --- a/app/serializers/activitypub/encrypted_message_serializer.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::EncryptedMessageSerializer < ActivityPub::Serializer - context :security - - context_extensions :olm - - class DeviceSerializer < ActivityPub::Serializer - attributes :type, :device_id - - def type - 'Device' - end - - def device_id - object - end - end - - class DigestSerializer < ActivityPub::Serializer - attributes :type, :digest_algorithm, :digest_value - - def type - 'Digest' - end - - def digest_algorithm - 'http://www.w3.org/2000/09/xmldsig#hmac-sha256' - end - - def digest_value - object - end - end - - attributes :type, :message_type, :cipher_text, :message_franking - - has_one :attributed_to, serializer: DeviceSerializer - has_one :to, serializer: DeviceSerializer - has_one :digest, serializer: DigestSerializer - - def type - 'EncryptedMessage' - end - - def attributed_to - object.source_device.device_id - end - - def to - object.target_device_id - end - - def message_type - object.type - end - - def cipher_text - object.body - end -end diff --git a/app/serializers/activitypub/one_time_key_serializer.rb b/app/serializers/activitypub/one_time_key_serializer.rb deleted file mode 100644 index 5932eb5b55..0000000000 --- a/app/serializers/activitypub/one_time_key_serializer.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::OneTimeKeySerializer < ActivityPub::Serializer - context :security - - context_extensions :olm - - class SignatureSerializer < ActivityPub::Serializer - attributes :type, :signature_value - - def type - 'Ed25519Signature' - end - - def signature_value - object.signature - end - end - - attributes :key_id, :type, :public_key_base64 - - has_one :signature, serializer: SignatureSerializer - - def type - 'Curve25519Key' - end - - def public_key_base64 - object.key - end - - def signature - object - end -end diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index 19fa5ddec7..c87f14f26b 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -2,12 +2,40 @@ class OEmbedSerializer < ActiveModel::Serializer INLINE_STYLES = { - blockquote: 'max-width: 540px; min-width: 270px; background:#FCF8FF; border: 1px solid #C9C4DA; border-radius: 8px; overflow: hidden; margin: 0; padding: 0;', - a: "color: #1C1A25; text-decoration: none; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 24px; font-size: 14px; line-height: 20px; letter-spacing: 0.25px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif;", # rubocop:disable Layout/LineLength - div0: 'margin-top: 16px; color: #787588;', - div1: 'font-weight: 500;', + blockquote: <<~CSS.squish, + background: #FCF8FF; + border-radius: 8px; + border: 1px solid #C9C4DA; + margin: 0; + max-width: 540px; + min-width: 270px; + overflow: hidden; + padding: 0; + CSS + status_link: <<~CSS.squish, + align-items: center; + color: #1C1A25; + display: flex; + flex-direction: column; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif; + font-size: 14px; + justify-content: center; + letter-spacing: 0.25px; + line-height: 20px; + padding: 24px; + text-decoration: none; + CSS + div_account: <<~CSS.squish, + color: #787588; + margin-top: 16px; + CSS + div_view: <<~CSS.squish, + font-weight: 500; + CSS }.freeze + DEFAULT_WIDTH = 400 + include RoutingHelper include ActionView::Helpers::TagHelper @@ -46,10 +74,10 @@ class OEmbedSerializer < ActiveModel::Serializer def html <<~HTML.squish
- + -
Post by @#{object.account.pretty_acct}@#{provider_name}
-
View on Mastodon
+
Post by @#{object.account.pretty_acct}@#{provider_name}
+
View on Mastodon
@@ -57,10 +85,10 @@ class OEmbedSerializer < ActiveModel::Serializer end def width - instance_options[:width] + (instance_options[:width] || DEFAULT_WIDTH).to_i end def height - instance_options[:height] + instance_options[:height].presence&.to_i end end diff --git a/app/serializers/rest/encrypted_message_serializer.rb b/app/serializers/rest/encrypted_message_serializer.rb deleted file mode 100644 index 80c26d060e..0000000000 --- a/app/serializers/rest/encrypted_message_serializer.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class REST::EncryptedMessageSerializer < ActiveModel::Serializer - attributes :id, :account_id, :device_id, - :type, :body, :digest, :message_franking, - :created_at - - def id - object.id.to_s - end - - def account_id - object.from_account_id.to_s - end - - def device_id - object.from_device_id - end -end diff --git a/app/serializers/rest/keys/claim_result_serializer.rb b/app/serializers/rest/keys/claim_result_serializer.rb deleted file mode 100644 index 145044f557..0000000000 --- a/app/serializers/rest/keys/claim_result_serializer.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::ClaimResultSerializer < ActiveModel::Serializer - attributes :account_id, :device_id, :key_id, :key, :signature - - def account_id - object.account.id.to_s - end -end diff --git a/app/serializers/rest/keys/device_serializer.rb b/app/serializers/rest/keys/device_serializer.rb deleted file mode 100644 index f9b821b79c..0000000000 --- a/app/serializers/rest/keys/device_serializer.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::DeviceSerializer < ActiveModel::Serializer - attributes :device_id, :name, :identity_key, - :fingerprint_key -end diff --git a/app/serializers/rest/keys/query_result_serializer.rb b/app/serializers/rest/keys/query_result_serializer.rb deleted file mode 100644 index 8f8bdde289..0000000000 --- a/app/serializers/rest/keys/query_result_serializer.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class REST::Keys::QueryResultSerializer < ActiveModel::Serializer - attributes :account_id - - has_many :devices, serializer: REST::Keys::DeviceSerializer - - def account_id - object.account.id.to_s - end -end diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index e2ecdef165..46cab6caf9 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -49,7 +49,7 @@ class ActivityPub::FetchRepliesService < BaseService rescue Mastodon::UnexpectedResponseError => e raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present? - fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { with_query_string: true }) + fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { omit_query_string: false }) end end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 1e2d614d72..a7422b5d02 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -108,7 +108,6 @@ class ActivityPub::ProcessAccountService < BaseService def set_immediate_attributes! @account.featured_collection_url = @json['featured'] || '' - @account.devices_url = @json['devices'] || '' @account.display_name = @json['name'] || '' @account.note = @json['summary'] || '' @account.locked = @json['manuallyApprovesFollowers'] || false diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 328d8ae8f8..0c03267d43 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -13,7 +13,6 @@ class DeleteAccountService < BaseService conversation_mutes conversations custom_filters - devices domain_blocks featured_tags follow_requests @@ -40,7 +39,6 @@ class DeleteAccountService < BaseService conversation_mutes conversations custom_filters - devices domain_blocks featured_tags follow_requests diff --git a/app/services/deliver_to_device_service.rb b/app/services/deliver_to_device_service.rb deleted file mode 100644 index 71711945c0..0000000000 --- a/app/services/deliver_to_device_service.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -class DeliverToDeviceService < BaseService - include Payloadable - - class EncryptedMessage < ActiveModelSerializers::Model - attributes :source_account, :target_account, :source_device, - :target_device_id, :type, :body, :digest, - :message_franking - end - - def call(source_account, source_device, options = {}) - @source_account = source_account - @source_device = source_device - @target_account = Account.find(options[:account_id]) - @target_device_id = options[:device_id] - @body = options[:body] - @type = options[:type] - @hmac = options[:hmac] - - set_message_franking! - - if @target_account.local? - deliver_to_local! - else - deliver_to_remote! - end - end - - private - - def set_message_franking! - @message_franking = message_franking.to_token - end - - def deliver_to_local! - target_device = @target_account.devices.find_by!(device_id: @target_device_id) - - target_device.encrypted_messages.create!( - from_account: @source_account, - from_device_id: @source_device.device_id, - type: @type, - body: @body, - digest: @hmac, - message_franking: @message_franking - ) - end - - def deliver_to_remote! - ActivityPub::DeliveryWorker.perform_async( - Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_encrypted_message(encrypted_message), ActivityPub::ActivitySerializer)), - @source_account.id, - @target_account.inbox_url - ) - end - - def message_franking - MessageFranking.new( - source_account_id: @source_account.id, - target_account_id: @target_account.id, - hmac: @hmac, - timestamp: Time.now.utc - ) - end - - def encrypted_message - EncryptedMessage.new( - source_account: @source_account, - target_account: @target_account, - source_device: @source_device, - target_device_id: @target_device_id, - type: @type, - body: @body, - digest: @hmac, - message_franking: @message_franking - ) - end -end diff --git a/app/services/keys/claim_service.rb b/app/services/keys/claim_service.rb deleted file mode 100644 index ebce9cce7d..0000000000 --- a/app/services/keys/claim_service.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -class Keys::ClaimService < BaseService - HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze - - class Result < ActiveModelSerializers::Model - attributes :account, :device_id, :key_id, - :key, :signature - - def initialize(account, device_id, key_attributes = {}) - super( - account: account, - device_id: device_id, - key_id: key_attributes[:key_id], - key: key_attributes[:key], - signature: key_attributes[:signature], - ) - end - end - - def call(source_account, target_account_id, device_id) - @source_account = source_account - @target_account = Account.find(target_account_id) - @device_id = device_id - - if @target_account.local? - claim_local_key! - else - claim_remote_key! - end - rescue ActiveRecord::RecordNotFound - nil - end - - private - - def claim_local_key! - device = @target_account.devices.find_by(device_id: @device_id) - key = nil - - ApplicationRecord.transaction do - key = device.one_time_keys.order(Arel.sql('random()')).first! - key.destroy! - end - - @result = Result.new(@target_account, @device_id, key) - end - - def claim_remote_key! - query_result = QueryService.new.call(@target_account) - device = query_result.find(@device_id) - - return unless device.present? && device.valid_claim_url? - - json = fetch_resource_with_post(device.claim_url) - - return unless json.present? && json['publicKeyBase64'].present? - - @result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue')) - rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" } - nil - end - - def fetch_resource_with_post(uri) - build_post_request(uri).perform do |response| - raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) - - body_to_json(response.body_with_limit) if response.code == 200 - end - end - - def build_post_request(uri) - Request.new(:post, uri).tap do |request| - request.on_behalf_of(@source_account) - request.add_headers(HEADERS) - end - end -end diff --git a/app/services/keys/query_service.rb b/app/services/keys/query_service.rb deleted file mode 100644 index 33e13293f3..0000000000 --- a/app/services/keys/query_service.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -class Keys::QueryService < BaseService - include JsonLdHelper - - class Result < ActiveModelSerializers::Model - attributes :account, :devices - - def initialize(account, devices) - super( - account: account, - devices: devices || [], - ) - end - - def find(device_id) - @devices.find { |device| device.device_id == device_id } - end - end - - class Device < ActiveModelSerializers::Model - attributes :device_id, :name, :identity_key, :fingerprint_key - - def initialize(attributes = {}) - super( - device_id: attributes[:device_id], - name: attributes[:name], - identity_key: attributes[:identity_key], - fingerprint_key: attributes[:fingerprint_key], - ) - @claim_url = attributes[:claim_url] - end - - def valid_claim_url? - return false if @claim_url.blank? - - begin - parsed_url = Addressable::URI.parse(@claim_url).normalize - rescue Addressable::URI::InvalidURIError - return false - end - - %w(http https).include?(parsed_url.scheme) && parsed_url.host.present? - end - end - - def call(account) - @account = account - - if @account.local? - query_local_devices! - else - query_remote_devices! - end - - Result.new(@account, @devices) - end - - private - - def query_local_devices! - @devices = @account.devices.map { |device| Device.new(device) } - end - - def query_remote_devices! - return if @account.devices_url.blank? - - json = fetch_resource(@account.devices_url) - - return if json['items'].blank? - - @devices = as_array(json['items']).map do |device| - Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim']) - end - rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug { "Querying devices for #{@account.acct} failed: #{e}" } - nil - end -end diff --git a/app/validators/ed25519_key_validator.rb b/app/validators/ed25519_key_validator.rb deleted file mode 100644 index adf49296b2..0000000000 --- a/app/validators/ed25519_key_validator.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class Ed25519KeyValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - return if value.blank? - - key = Base64.decode64(value) - - record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key) - end - - private - - def verified?(key) - Ed25519.validate_key_bytes(key) - rescue ArgumentError - false - end -end diff --git a/app/validators/ed25519_signature_validator.rb b/app/validators/ed25519_signature_validator.rb deleted file mode 100644 index 0e74c231ec..0000000000 --- a/app/validators/ed25519_signature_validator.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class Ed25519SignatureValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - return if value.blank? - - verify_key = Ed25519::VerifyKey.new(Base64.decode64(option_to_value(record, :verify_key))) - signature = Base64.decode64(value) - message = option_to_value(record, :message) - - record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message) - end - - private - - def verified?(verify_key, signature, message) - verify_key.verify(signature, message) - rescue Ed25519::VerifyError, ArgumentError - false - end - - def option_to_value(record, key) - if options[key].is_a?(Proc) - options[key].call(record) - else - record.public_send(options[key]) - end - end -end diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index e0870503d6..f4630ed25a 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -18,7 +18,7 @@ - if status.application = status.application.name · - = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do + = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) - if status.edited? · diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml index b43b8dfff9..93daf25f31 100644 --- a/app/views/admin/trends/links/preview_card_providers/index.html.haml +++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml @@ -12,7 +12,7 @@ %li= filter_link_to t('generic.all'), status: nil %li= filter_link_to t('admin.trends.approved'), status: 'approved' %li= filter_link_to t('admin.trends.rejected'), status: 'rejected' - %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.pending_review.count})"], ' '), status: 'pending_review' + %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.unreviewed.count})"], ' '), status: 'pending_review' .back-link = link_to admin_trends_links_path do = material_symbol 'chevron_left' diff --git a/app/workers/push_encrypted_message_worker.rb b/app/workers/push_encrypted_message_worker.rb deleted file mode 100644 index 6bee12675b..0000000000 --- a/app/workers/push_encrypted_message_worker.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class PushEncryptedMessageWorker - include Sidekiq::Worker - include Redisable - - def perform(encrypted_message_id) - encrypted_message = EncryptedMessage.find(encrypted_message_id) - message = InlineRenderer.render(encrypted_message, nil, :encrypted_message) - timeline_id = "timeline:#{encrypted_message.device.account_id}:#{encrypted_message.device.device_id}" - - redis.publish(timeline_id, Oj.dump(event: :encrypted_message, payload: message)) - rescue ActiveRecord::RecordNotFound - true - end -end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 86fde3cacf..b47e76c08b 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -118,8 +118,7 @@ Doorkeeper.configure do :'admin:write:domain_blocks', :'admin:write:ip_blocks', :'admin:write:email_domain_blocks', - :'admin:write:canonical_email_blocks', - :crypto + :'admin:write:canonical_email_blocks' # Change the way client credentials are retrieved from the request object. # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ba459e19f2..2b0563f4d3 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -19,7 +19,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'CLI' inflect.acronym 'DeepL' inflect.acronym 'DSL' - inflect.acronym 'Ed25519' inflect.acronym 'JsonLd' inflect.acronym 'OEmbed' inflect.acronym 'OStatus' diff --git a/config/locales/activerecord.ko.yml b/config/locales/activerecord.ko.yml index 294d614bca..6d437b72b0 100644 --- a/config/locales/activerecord.ko.yml +++ b/config/locales/activerecord.ko.yml @@ -15,6 +15,12 @@ ko: user/invite_request: text: 이유 errors: + attributes: + domain: + invalid: 올바른 도메인 네임이 아닙니다 + messages: + invalid_domain_on_line: "%{value}는 올바른 도메인 네임이 아닙니다" + too_many_lines: "%{limit}줄 제한을 초과합니다" models: account: attributes: diff --git a/config/locales/activerecord.ru.yml b/config/locales/activerecord.ru.yml index 92d85af4d9..203d8e2c34 100644 --- a/config/locales/activerecord.ru.yml +++ b/config/locales/activerecord.ru.yml @@ -15,6 +15,12 @@ ru: user/invite_request: text: Причина errors: + attributes: + domain: + invalid: не является действующим доменным именем + messages: + invalid_domain_on_line: "%{value} Не является действительным доменным именем" + too_many_lines: Превышает предел %{limit} строк models: account: attributes: diff --git a/config/locales/an.yml b/config/locales/an.yml index 589bb39836..2f181e0757 100644 --- a/config/locales/an.yml +++ b/config/locales/an.yml @@ -953,7 +953,6 @@ an: crypto: errors: invalid_key: no ye una clau Ed25519 u Curve25519 valida - invalid_signature: no ye una sinyatura Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 7512e03fd5..81705acba0 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1178,7 +1178,6 @@ ar: crypto: errors: invalid_key: ليس بمفتاح Ed25519 أو Curve25519 صالح - invalid_signature: ليس بتوقيع Ed25519 صالح date: formats: default: "%d %b %Y" diff --git a/config/locales/ast.yml b/config/locales/ast.yml index be3441507e..a7d4a9917e 100644 --- a/config/locales/ast.yml +++ b/config/locales/ast.yml @@ -486,7 +486,6 @@ ast: crypto: errors: invalid_key: nun ye una clave ed25519 o curve25519 válida - invalid_signature: nun ye una clave ed25519 válida datetime: distance_in_words: about_x_hours: "%{count} h" diff --git a/config/locales/be.yml b/config/locales/be.yml index 48ca5751cc..d45f443b75 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1189,7 +1189,6 @@ be: crypto: errors: invalid_key: гэта не сапраўдны Ed25519 або Curve25519 ключ - invalid_signature: гэта не сапраўдная Ed25519 сігнатура date: formats: default: "%d.%m.%Y" diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 604eeca480..e2c246827a 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1115,7 +1115,6 @@ bg: crypto: errors: invalid_key: не е валиден ключ Ed25519 или Curve25519 - invalid_signature: не е валиден подпис Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index d985a2ac42..33a1d4e88f 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1171,7 +1171,6 @@ ca: crypto: errors: invalid_key: no és una clau Ed25519 o Curve25519 vàlida - invalid_signature: no és una signatura Ed25519 vàlida date: formats: default: "%b %d, %Y" diff --git a/config/locales/ckb.yml b/config/locales/ckb.yml index 8af3d86388..bc668d2ce4 100644 --- a/config/locales/ckb.yml +++ b/config/locales/ckb.yml @@ -608,7 +608,6 @@ ckb: crypto: errors: invalid_key: کلیلی باوڕپێکراو Ed25519 یان Curve25519 دروست نییە - invalid_signature: واژووی Ed25519 بڕوادار نییە date: formats: default: "%b %d, %Y" diff --git a/config/locales/co.yml b/config/locales/co.yml index b072e5e4ef..58ddd7d01b 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -569,7 +569,6 @@ co: crypto: errors: invalid_key: ùn hè micca una chjave Ed25519 o Curve25519 valida - invalid_signature: ùn hè micca una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 1000442870..b7fc8ab1b0 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1149,7 +1149,6 @@ cs: crypto: errors: invalid_key: není platný klíč Ed25519 nebo Curve25519 - invalid_signature: není platný podpis typu Ed25519 date: formats: default: "%d. %b %Y" diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 9d3c0c82f3..c2c193d943 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -1246,7 +1246,6 @@ cy: crypto: errors: invalid_key: ddim yn allwedd Ed25519 na Curve25519 dilys - invalid_signature: ddim yn llofnod Ed25519 dilys date: formats: default: "%b %d %Y" diff --git a/config/locales/da.yml b/config/locales/da.yml index 6f781742a8..a177b97de7 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1174,7 +1174,6 @@ da: crypto: errors: invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøgle - invalid_signature: er ikke en gylidig Ed25519-signatur date: formats: default: "%d. %b %Y" diff --git a/config/locales/de.yml b/config/locales/de.yml index 040ddaaf69..85e24c230e 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1174,7 +1174,6 @@ de: crypto: errors: invalid_key: ist kein gültiger Ed25519- oder Curve25519-Schlüssel - invalid_signature: ist keine gültige Ed25519-Signatur date: formats: default: "%d. %b %Y" diff --git a/config/locales/doorkeeper.es-MX.yml b/config/locales/doorkeeper.es-MX.yml index b5987676d2..49ff9a1e43 100644 --- a/config/locales/doorkeeper.es-MX.yml +++ b/config/locales/doorkeeper.es-MX.yml @@ -167,7 +167,7 @@ es-MX: admin:write:reports: realizar acciones de moderación en informes crypto: usar cifrado de extremo a extremo follow: seguir, bloquear, desbloquear y dejar de seguir cuentas - profile: leer sólo la información del perfil de tu cuenta + profile: leer solamente la información del perfil de tu cuenta push: recibir tus notificaciones push read: leer los datos de tu cuenta read:accounts: ver información de cuentas diff --git a/config/locales/el.yml b/config/locales/el.yml index 1f408e26ea..0da957cdb2 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1128,7 +1128,6 @@ el: crypto: errors: invalid_key: δεν είναι έγκυρο κλειδί Ed25519 ή Curve25519 - invalid_signature: δεν είναι έγκυρη υπογραφή Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 56255f5d7a..577978ce88 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1174,7 +1174,6 @@ en-GB: crypto: errors: invalid_key: is not a valid Ed25519 or Curve25519 key - invalid_signature: is not a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/en.yml b/config/locales/en.yml index b1c100da0c..625f53f2e5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1174,7 +1174,6 @@ en: crypto: errors: invalid_key: is not a valid Ed25519 or Curve25519 key - invalid_signature: is not a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 46c6cbcf86..7ebf074819 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -1041,7 +1041,6 @@ eo: crypto: errors: invalid_key: 올바른 Ed25519 혹은 Curve25519 키가 아닙니다 - invalid_signature: 올바른 Ed25519 시그니처가 아닙니다 date: formats: default: "%Y-%b-%d" diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 4d60d080a2..9f1dc46c9d 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1174,7 +1174,6 @@ es-AR: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d de %b de %Y" diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index ebe26c3f14..8aac7cbb47 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -175,7 +175,7 @@ es-MX: approve_appeal: Aprobar apelación approve_user: Aprobar Usuario assigned_to_self_report: Asignar Reporte - change_email_user: Cambiar Correo Electrónico del Usuario + change_email_user: Cambiar correo electrónico por usuario change_role_user: Cambiar rol del usuario confirm_user: Confirmar Usuario create_account_warning: Crear Advertencia @@ -202,10 +202,10 @@ es-MX: destroy_user_role: Destruir Rol disable_2fa_user: Deshabilitar 2FA disable_custom_emoji: Deshabilitar Emoji Personalizado - disable_sign_in_token_auth_user: Deshabilitar la Autenticación por Token de Correo Electrónico para el Usuario + disable_sign_in_token_auth_user: Deshabilitar la autenticación por token de correo electrónico para el usuario disable_user: Deshabilitar Usuario enable_custom_emoji: Habilitar Emoji Personalizado - enable_sign_in_token_auth_user: Habilitar la Autenticación por Token de Correo Electrónico para el Usuario + enable_sign_in_token_auth_user: Habilitar la autenticación por token de correo electrónico para el usuario enable_user: Habilitar Usuario memorialize_account: Transformar en Cuenta Conmemorativa promote_user: Promover Usuario @@ -761,7 +761,7 @@ es-MX: desc_html: Esto se basa en scripts externos de hCaptcha, que pueden suponer una preocupación de seguridad y privacidad. Además, esto puede volver el proceso de registro significativamente menos accesible para algunas personas (especialmente con discapacidades). Por estas razones, por favor, considera medidas alternativas como el registro por aprobación manual o con invitación. title: Solicita a los nuevos usuarios que resuelvan un CAPTCHA para confirmar su cuenta content_retention: - danger_zone: Zona peligrosa + danger_zone: Zona de peligro preamble: Controlar cómo el contenido generado por el usuario se almacena en Mastodon. title: Retención de contenido default_noindex: @@ -896,7 +896,7 @@ es-MX: reviewed: Revisada title: Estado trendable: Puede ser tendencia - unreviewed: Sin revisar + unreviewed: No revisado usable: Disponible name: Nombre newest: Más reciente @@ -944,9 +944,9 @@ es-MX: statuses: allow: Permitir publicación allow_account: Permitir autor - confirm_allow: "¿Estás seguro de que deseas permitir los estados seleccionados?" + confirm_allow: "¿Estás seguro de que deseas permitir las publicaciones seleccionadas?" confirm_allow_account: "¿Estás seguro de que deseas permitir las cuentas seleccionadas?" - confirm_disallow: "¿Estás seguro de que deseas restringir los estados seleccionados?" + confirm_disallow: "¿Estás seguro de que deseas restringir las publicaciones seleccionadas?" confirm_disallow_account: "¿Estás seguro de que deseas restringir las cuentas seleccionadas?" description_html: Estos son publicaciones que su servidor conoce que están siendo compartidas y marcadas como favoritas mucho en este momento. Pueden ayudar a tus usuarios nuevos y retornantes a encontrar más gente a la que seguir. No hay mensajes que se muestren públicamente hasta que apruebes el autor y el autor permita que su cuenta sea sugerida a otros. También puedes permitir o rechazar mensajes individuales. disallow: Rechazar publicación @@ -1152,7 +1152,7 @@ es-MX: title: Crear cuenta de Mastodon en %{domain}. status: account_status: Estado de la cuenta - confirming: Esperando confirmación de correo electrónico. + confirming: Esperando a que se complete la confirmación por correo electrónico. functional: Tu cuenta está completamente operativa. pending: Tu solicitud está pendiente de revisión por nuestro personal. Eso puede tardar un tiempo. Recibirás un correo electrónico cuando tu solicitud sea aprobada. redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}. @@ -1174,7 +1174,6 @@ es-MX: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d %b %Y" @@ -1538,7 +1537,7 @@ es-MX: update: subject: "%{name} editó una publicación" notifications: - administration_emails: Notificaciones administrativas por correo + administration_emails: Notificaciones de administración por correo electrónico email_events: Eventos para notificaciones por correo electrónico email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:' number: diff --git a/config/locales/es.yml b/config/locales/es.yml index c652876f3a..2815ada779 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1174,7 +1174,6 @@ es: crypto: errors: invalid_key: no es una clave Ed25519 o Curve25519 válida - invalid_signature: no es una firma Ed25519 válida date: formats: default: "%d %b %Y" diff --git a/config/locales/et.yml b/config/locales/et.yml index aca4f93c4d..60f98a471e 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1174,7 +1174,6 @@ et: crypto: errors: invalid_key: ei ole õige Ed25519 ega Curve25519 võti - invalid_signature: ei ole õige Ed25519 allkiri date: formats: default: "%d. %b %Y" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 6260033990..7c7f995a71 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1091,7 +1091,6 @@ eu: crypto: errors: invalid_key: ez da baliozko Ed25519 edo Curve25519 gakoa - invalid_signature: ez da baliozko Ed25519 sinadura date: formats: default: "%Y(e)ko %b %d" diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 996c8d6cd2..c2c22af4c4 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -971,7 +971,6 @@ fa: crypto: errors: invalid_key: یک کلید معتبر Ed25519 یا Curve25519 نیست - invalid_signature: یک امضای معتبر Ed25519 نیست date: formats: default: "%d %b %Y" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index b48b499bbe..5df82bff9f 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1174,7 +1174,6 @@ fi: crypto: errors: invalid_key: ei ole kelvollinen Ed25519- tai Curve25519-avain - invalid_signature: ei ole kelvollinen Ed25519-allekirjoitus date: formats: default: "%b %d, %Y" diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 266b73bb10..ce21aa3be7 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1174,7 +1174,6 @@ fo: crypto: errors: invalid_key: er ikki ein gildur Ed25519 ella Curve25519 lykil - invalid_signature: er ikki ein gildug Ed25519 undirskrift date: formats: default: "%b %d, %Y" diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index b70a515fd1..3f9252ffab 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1175,7 +1175,6 @@ fr-CA: crypto: errors: invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide - invalid_signature: n’est pas une signature Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index bc616d2896..cb76ae2243 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1175,7 +1175,6 @@ fr: crypto: errors: invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide - invalid_signature: n’est pas une signature Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 6afdecd556..8b854494d4 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1164,7 +1164,6 @@ fy: crypto: errors: invalid_key: is gjin jildige Ed25519- of Curve25519-kaai - invalid_signature: is gjin jildige Ed25519-hantekening date: formats: default: "%d %b %Y" diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 1071871c95..a6369354cd 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1228,7 +1228,6 @@ ga: crypto: errors: invalid_key: nach eochair bhailí Ed25519 nó Curve25519 í - invalid_signature: nach síniú bailí Ed25519 é date: formats: default: "%b %d, %Y" diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 5e63b5bd23..b5cbc4a73e 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -1210,7 +1210,6 @@ gd: crypto: errors: invalid_key: "– chan e iuchair Ed25519 no Curve25519 dhligheach a th’ ann" - invalid_signature: "– chan e soidhneadh Ed25519 dligheach a th’ ann" date: formats: default: "%d %b %Y" diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 58fd2d9bab..9813514a7b 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1174,7 +1174,6 @@ gl: crypto: errors: invalid_key: non é unha chave Ed25519 ou Curve25519 válida - invalid_signature: non é unha sinatura Ed25519 válida date: formats: default: "%d %b, %Y" diff --git a/config/locales/he.yml b/config/locales/he.yml index 7a2d0a1d98..13a1f6f05d 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1210,7 +1210,6 @@ he: crypto: errors: invalid_key: זהו לא מפתח Ed25519 או Curve25519 קביל - invalid_signature: היא לא חתימת Ed25519 קבילה date: formats: default: "%b %d, %Y" diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 10c7506b01..9767c48834 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1174,7 +1174,6 @@ hu: crypto: errors: invalid_key: érvénytelen Ed25519 vagy Curve25519 kulcs - invalid_signature: érvénytelen Ed25519 aláírás date: formats: default: "%Y. %b %d." diff --git a/config/locales/hy.yml b/config/locales/hy.yml index 80dbc77991..1fda020c07 100644 --- a/config/locales/hy.yml +++ b/config/locales/hy.yml @@ -495,7 +495,6 @@ hy: crypto: errors: invalid_key: անվաւեր Ed25519 կամ Curve25519 բանալի - invalid_signature: անվաւեր Ed25519 բանալի date: formats: default: "%b %d, %Y" diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 957bae3991..6632af061e 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -1158,7 +1158,6 @@ ia: crypto: errors: invalid_key: non es un clave Ed25519 o Curve25519 valide - invalid_signature: non es un signatura Ed25519 valide date: formats: default: "%d %b %Y" diff --git a/config/locales/id.yml b/config/locales/id.yml index 222d2b5680..96a5022a7c 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -936,7 +936,6 @@ id: crypto: errors: invalid_key: bukan kunci Ed25519 atau Curve25519 yang valid - invalid_signature: bukan tanda tangan Ed25519 yang valid date: formats: default: "%d %b %Y" diff --git a/config/locales/ie.yml b/config/locales/ie.yml index 6a79686f48..513a8eda7e 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -1089,7 +1089,6 @@ ie: crypto: errors: invalid_key: ne es un valid clave Ed25519 o Curve25519 - invalid_signature: ne es un valid signatura Ed25519 date: formats: default: "%d.%m.%Y" diff --git a/config/locales/io.yml b/config/locales/io.yml index dbbe228e72..97cc417df0 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -1064,7 +1064,6 @@ io: crypto: errors: invalid_key: ne esas valida klefo Ed25519 o Curve25519 - invalid_signature: ne esas valida parafo Ed25519 date: formats: default: "%d %b, %Y" diff --git a/config/locales/is.yml b/config/locales/is.yml index 78dfd6048f..590805ad30 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1178,7 +1178,6 @@ is: crypto: errors: invalid_key: er ekki gildur Ed25519 eða Curve25519-lykill - invalid_signature: er ekki gild Ed25519 undirritun date: formats: default: "%d. %b, %Y" diff --git a/config/locales/it.yml b/config/locales/it.yml index 792add14e3..fe6fec17d7 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1176,7 +1176,6 @@ it: crypto: errors: invalid_key: non è una chiave Ed25519 o Curve25519 valida - invalid_signature: non è una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 13f59e981b..ed6293e1ca 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1146,7 +1146,6 @@ ja: crypto: errors: invalid_key: 有効なEd25519またはCurve25519キーではありません - invalid_signature: 有効なEd25519署名ではありません date: formats: default: "%Y年%m月%d日" diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 7c3d526702..7044983ac9 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -703,6 +703,7 @@ kab: prev: Win iɛeddan preferences: other: Wiyaḍ + posting_defaults: Iɣewwaṛen n usuffeɣ imezwura privacy: privacy: Tabaḍnit search: Anadi @@ -779,6 +780,7 @@ kab: import: Kter import_and_export: Taktert d usifeḍ migrate: Tunigin n umiḍan + notifications: Alɣuten s imayl preferences: Imenyafen profile: Ameɣnu relationships: Imeḍfaṛen akked wid i teṭṭafaṛeḍ diff --git a/config/locales/ko.yml b/config/locales/ko.yml index cbcc09a4da..216e468762 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -22,6 +22,7 @@ ko: admin: account_actions: action: 조치 취하기 + already_silenced: 이 계정은 이미 제한되었습니다. already_suspended: 이 계정은 이미 정지되었습니다. title: "%{acct} 계정에 중재 취하기" account_moderation_notes: @@ -1143,6 +1144,12 @@ ko: view_strikes: 내 계정에 대한 과거 중재 기록 보기 too_fast: 너무 빠르게 양식이 제출되었습니다, 다시 시도하세요. use_security_key: 보안 키 사용 + author_attribution: + example_title: 예시 텍스트 + hint_html: 링크가 마스토돈에 공유될 때 내가 어떻게 표시될 지를 제어합니다. + more_from_html: "%{name}의 게시물 더 보기" + s_blog: "%{name}의 블로그" + title: 작성자 기여 challenge: confirm: 계속 hint_html: "팁: 한 시간 동안 다시 암호를 묻지 않을 것입니다." @@ -1151,7 +1158,6 @@ ko: crypto: errors: invalid_key: 유효하지 않은 Ed25519 또는 Curve25519 키 - invalid_signature: 유효하지 않은 Ed25519 서명 date: formats: default: "%Y-%m-%d" @@ -1907,6 +1913,7 @@ ko: instructions_html: 웹사이트에 아래 코드를 복사해 붙여 넣으세요. 그리고 "프로필 수정" 탭에서 그 웹사이트 주소를 프로필의 추가 필드 중 하나에 넣고 변경사항을 저장하세요. verification: 검증 verified_links: 인증된 링크들 + website_verification: 웹사이트 인증 webauthn_credentials: add: 보안 키 추가 create: diff --git a/config/locales/ku.yml b/config/locales/ku.yml index 6b80e32ba8..08843f645e 100644 --- a/config/locales/ku.yml +++ b/config/locales/ku.yml @@ -950,7 +950,6 @@ ku: crypto: errors: invalid_key: ed25519 ne derbasdare ne jî Curve25519 kilîta - invalid_signature: Ed25519 ne îmzeyek derbasdar e date: formats: default: "%b%d%Y" diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 2f5eb1553e..3d1f1a61e2 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -1122,7 +1122,6 @@ lad: crypto: errors: invalid_key: no es una yave Ed25519 o Curve25519 valida - invalid_signature: no es una firma Ed25519 valida date: formats: default: "%d %b %Y" diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 55a0751811..4aeec5ceca 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1,7 +1,7 @@ --- lv: about: - about_mastodon_html: 'Nākotnes sociālais tīkls: bez reklāmām, bez korporatīvās uzraudzības, ētisks dizains un decentralizācija! Pārvaldi savus datus ar Mastodon!' + about_mastodon_html: 'Nākotnes sabiedriskais tīkls: bez reklāmām, bez korporatīvās novērošanas, ētiska projektēšana un decentralizēšana. Pārvaldi savus datus ar Mastodon!' contact_missing: Nav uzstādīts contact_unavailable: N/A hosted_on: Mastodon mitināts %{domain} @@ -613,6 +613,7 @@ lv: created_at: Ziņoti delete_and_resolve: Izdzēst rakstus forwarded: Pārsūtīti + forwarded_replies_explanation: Šis ziņojums ir no attāla lietotāja un par attālu saturu. Tas tika pārvirzīts šeit, jo saturs, par kuru tika ziņots, ir atbilde vienam no šī servera lietotājiem. forwarded_to: Pārsūtīti %{domain} mark_as_resolved: Atzīmēt kā atrisinātu mark_as_sensitive: Atzīmēt kā sensitīvu @@ -633,6 +634,7 @@ lv: report: 'Ziņojums #%{id}' reported_account: Ziņotais konts reported_by: Ziņoja + reported_with_application: Ziņots no lietotnes resolved: Atrisināts resolved_msg: Ziņojums veiksmīgi atrisināts! skip_to_actions: Pāriet uz darbībām @@ -876,7 +878,13 @@ lv: pending_review: Gaida pārskatīšanu review_requested: Pieprasīta pārskatīšana reviewed: Pārskatīts + title: Stāvoklis + name: Nosaukums + newest: Jaunākie + oldest: Vecākie + reset: Atiestatīt review: Pārskatīt stāvokli + search: Meklēt title: Tēmturi updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti title: Administrēšana @@ -1106,7 +1114,7 @@ lv: new_confirmation_instructions_sent: Pēc dažām minūtēm saņemsi jaunu e-pasta ziņojumu ar apstiprinājuma saiti. title: Pārbaudi savu iesūtni sign_in: - preamble_html: Jāpiesakās ar saviem %{domain} piekļuves datiem. Ja Tavs konts tiek mitināts citā serverī, Tu nevarēsi šeit pieteikties. + preamble_html: Jāpiesakās ar saviem %{domain} piekļuves datiem. Ja konts tiek mitināts citā serverī, šeit nevarēs pieteikties. title: Pierakstīties %{domain} sign_up: manual_review: Reģistrācijas domēnā %{domain} manuāli pārbauda mūsu moderatori. Lai palīdzētu mums apstrādāt tavu reģistrāciju, uzraksti mazliet par sevi un to, kāpēc vēlies kontu %{domain}. @@ -1121,6 +1129,9 @@ lv: view_strikes: Skati iepriekšējos brīdinājumus par savu kontu too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz. use_security_key: Lietot drošības atslēgu + author_attribution: + s_blog: "%{name} emuāri" + title: Autora attiecinājums challenge: confirm: Turpināt hint_html: "Padoms: Nākamās stundas laikā mēs tev vairs neprasīsim paroli." @@ -1129,7 +1140,6 @@ lv: crypto: errors: invalid_key: nav derīga Ed25519 vai Curve25519 atslēga - invalid_signature: nav derīgs Ed25519 paraksts date: formats: default: "%b %d, %Y" @@ -1158,6 +1168,9 @@ lv: before: 'Pirms turpināšanas lūgums uzmanīgi izlasīt šīs piezīmes:' caches: Citu serveru kešatmiņā saglabātais saturs var saglabāties data_removal: Tavas ziņas un citi dati tiks neatgriezeniski noņemti + email_change_html: Savu e-pasta adresi var mainīt bez sava konta izdzēšanas + email_contact_html: Ja tas joprojām nav saņemts, var nosūtīt e-pastu uz %{email}, lai saņemtu palīdzību + email_reconfirmation_html: Ja netiek saņemts apstiprinājuma e-pasta ziņojums, to var pieprasīt vēlreiz irreversible: Tu nevarēsi atjaunot vai atkārtoti aktivizēt savu kontu more_details_html: Plašāku informāciju skatīt privātuma politika. username_available: Tavs lietotājvārds atkal būs pieejams @@ -1377,6 +1390,7 @@ lv: '86400': 1 diena expires_in_prompt: Nekad generate: Ģenerēt uzaicinājuma saiti + invalid: Šis uzaicinājums nav derīgs invited_by: 'Tevi uzaicināja:' max_uses: one: 1 lietojums @@ -1395,6 +1409,7 @@ lv: authentication_methods: otp: divpakāpju autentifikācijas lietotne password: parole + sign_in_token: e-pasta drošības kods webauthn: drošības atslēgas description_html: Ja pamani darbības, kuras neatpazīsti, jāapsver iespēja nomainīt savu paroli un iespējot divpakāpju autentifikāciju. empty: Nav pieejama autentifikācijas vēsture @@ -1820,14 +1835,20 @@ lv: explanation: Šeit ir daži padomi, kā sākt darbu feature_action: Uzzināt vairāk feature_creativity: Mastodon nodrošina skaņas, video un attēlu ierakstus, pieejamības aprakstus, aptaujas, satura brīdinājumus, animētus profila attēlus, pielāgotas emocijzīmes, sīktēlu apgriešanas vadīklas un vēl, lai palīdzētu Tev sevi izpaust tiešsaistē. Vai Tu izplati savu mākslu, mūziku vai aplādes, Mastodon ir šeit ar Tevi. + feature_moderation_title: Moderēšana, kādai tai būtu jābūt follow_action: Sekot + follow_step: Sekošana aizraujošiem cilvēkiem ir viss, par ko ir Mastodon. follow_title: Pielāgo savu mājas barotni + follows_subtitle: Seko labi zināmiem kontiem follows_title: Kam sekot follows_view_more: Rādīt vairāk cilvēku, kuriem sekot hashtags_recent_count: one: "%{people} cilvēks pēdējās 2 dienās" other: "%{people} cilvēki pēdējās 2 dienās" zero: "%{people} cilvēku pēdējās divās dienās" + hashtags_subtitle: Izpēti, kas pēdējās divās dienāš ir piesasitījis cilvēku uzmanību + hashtags_title: Izplatīti tēmturi + hashtags_view_more: Skatīt vairāk izplatītu tēmturu post_action: Rakstīt post_step: Pasveicini pasauli ar tekstu, fotoattēliem, video vai aptaujām! post_title: Izveido savu pirmo ierakstu @@ -1843,6 +1864,7 @@ lv: invalid_otp_token: Nederīgs divfaktora kods otp_lost_help_html: Ja esi zaudējis piekļuvi abiem, tu vari sazināties ar %{email} rate_limited: Pārāk daudz autentifikācijas mēģinājumu, vēlāk jāmēģina vēlreiz. + seamless_external_login: Tu esi pieteicies caur ārēju pakalpojumu, tāpēc paroles un e-pasta iestatījumi nav pieejami. signed_in_as: 'Pieteicies kā:' verification: extra_instructions_html: Padoms: saite Tavā vietnē var būt neredzama. Svarīga daļa ir rel="me", kas novērš uzdošanos vietnēs ar lietotāju izveidotu saturu. Tu pat vari lapas galvenē izmantot tagu link, nevis a, taču HTML ir jābūt pieejamam bez JavaScript izpildīšanas. @@ -1851,6 +1873,7 @@ lv: instructions_html: Ievieto starpliktuvē un ielīmē tālāk norādīto kodu savas tīmekļvietnes HTML! Tad pievieno savas tīmekļvietnes adresi vienā no papildu laukiem savā profila cilnē "Labot profilu" un saglabā izmaiņas! verification: Pārbaude verified_links: Tavas verifikācijas saites + website_verification: Tīmekļvietnes apliecināšana webauthn_credentials: add: Pievienot jaunu drošības atslēgu create: diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 39c695a539..0c0ffb69bf 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -1051,7 +1051,6 @@ ms: crypto: errors: invalid_key: bukan kunci Ed25519 atau Curve25519 yang sah - invalid_signature: bukan tandatangan Ed25519 yang sah date: formats: default: "%b %d, %Y" diff --git a/config/locales/my.yml b/config/locales/my.yml index 92464523a0..6acaa34cdc 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1044,7 +1044,6 @@ my: crypto: errors: invalid_key: မှန်ကန်သော Ed25519 သို့မဟုတ် Curve25519 ကီး မဟုတ်ပါ။ - invalid_signature: မှန်ကန်သော Ed25519 လက်မှတ်မဟုတ်ပါ date: formats: default: "%b %d, %Y" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 63656991a8..afc4652bf6 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1174,7 +1174,6 @@ nl: crypto: errors: invalid_key: is geen geldige Ed25519- of Curve25519-sleutel - invalid_signature: is geen geldige Ed25519-handtekening date: formats: default: "%d %b %Y" diff --git a/config/locales/nn.yml b/config/locales/nn.yml index b7beeb4263..dcf571a792 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1174,7 +1174,6 @@ nn: crypto: errors: invalid_key: er ikkje ein gild Ed25519 eller Curve25519 nykel - invalid_signature: er ikkje ein gild Ed25519-signatur date: formats: default: "%d. %b, %Y" diff --git a/config/locales/no.yml b/config/locales/no.yml index 635ceedde4..1f0b6bacce 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1083,7 +1083,6 @@ crypto: errors: invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøkkel - invalid_signature: er ikke en gyldig Ed25519-signatur date: formats: default: "%d. %b, %Y" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f0d09cb2d3..314adf885f 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1210,7 +1210,6 @@ pl: crypto: errors: invalid_key: nie jest prawidłowym kluczem Ed25519 lub Curve25519 - invalid_signature: nie jest prawidłowym podpisem Ed25519 date: formats: default: "%d. %b %Y" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d1140f3645..7742a80569 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1174,7 +1174,6 @@ pt-BR: crypto: errors: invalid_key: não é uma chave Ed25519 ou Curve25519 válida - invalid_signature: não é uma assinatura Ed25519 válida date: formats: default: "%d %b, %Y" diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index f155490eb5..d6c5c4a6ff 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1174,7 +1174,6 @@ pt-PT: crypto: errors: invalid_key: não é uma chave Ed25519 ou Curve25519 válida - invalid_signature: não é uma assinatura Ed25519 válida date: formats: default: "%d %b %Y" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index d66dded89b..3877727490 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -28,6 +28,8 @@ ru: admin: account_actions: action: Выполнить действие + already_silenced: Эта учетная запись уже ограничена. + already_suspended: Действие этой учетной записи уже приостановлено. title: Произвести модерацию учётной записи %{acct} account_moderation_notes: create: Создать @@ -49,6 +51,7 @@ ru: title: Сменить e-mail для %{username} change_role: changed_msg: Роль успешно изменена! + edit_roles: Управление ролями пользователей label: Изменить роль no_role: Нет роли title: Изменить роль %{username} @@ -61,6 +64,7 @@ ru: demote: Разжаловать destroyed_msg: Данные %{username} поставлены в очередь на удаление disable: Заморозка + disable_sign_in_token_auth: Отключите аутентификацию с помощью маркера электронной почты disable_two_factor_authentication: Отключить 2FA disabled: Отключено display_name: Отображаемое имя @@ -69,6 +73,7 @@ ru: email: E-mail email_status: Статус e-mail enable: Включить + enable_sign_in_token_auth: Включите аутентификацию с помощью маркера электронной почты enabled: Включен enabled_msg: Учётная запись %{username} успешно разморожена followers: Подписчики @@ -135,6 +140,7 @@ ru: resubscribe: Переподписаться role: Роль search: Поиск + search_same_email_domain: Другие пользователи с тем же почтовым доменом search_same_ip: Другие пользователи с таким же IP security: Безопасность security_measures: @@ -180,17 +186,21 @@ ru: confirm_user: Подтверждение пользователей create_account_warning: Выдача предупреждения create_announcement: Создание объявлений + create_canonical_email_block: Создать блок электронной почты create_custom_emoji: Добавление эмодзи create_domain_allow: Разрешение доменов create_domain_block: Блокировка доменов + create_email_domain_block: Создать блок домена электронной почты create_ip_block: Создание правил для IP-адресов create_unavailable_domain: Добавление домена в список недоступных create_user_role: Создать роль demote_user: Разжалование пользователей destroy_announcement: Удаление объявлений + destroy_canonical_email_block: Удалить блок электронной почты destroy_custom_emoji: Удаление эмодзи destroy_domain_allow: Отзыв разрешений для доменов destroy_domain_block: Разблокировка доменов + destroy_email_domain_block: Удалить блок домена электронной почты destroy_instance: Очистить домен destroy_ip_block: Удаление правил для IP-адресов destroy_status: Удаление постов @@ -198,8 +208,10 @@ ru: destroy_user_role: Удалить роль disable_2fa_user: Отключение 2FA disable_custom_emoji: Отключение эмодзи + disable_sign_in_token_auth_user: Отключить аутентификацию пользователя с помощью токена электронной почты disable_user: Заморозка пользователей enable_custom_emoji: Включение эмодзи + enable_sign_in_token_auth_user: Включить аутентификацию пользователя с помощью токена электронной почты enable_user: Разморозка пользователей memorialize_account: Присвоение пользователям статуса «мемориала» promote_user: Повышение пользователей @@ -229,20 +241,26 @@ ru: approve_appeal_html: "%{name} одобрил апелляцию на умеренное решение от %{target}" approve_user_html: "%{name} утвердил(а) регистрацию %{target}" assigned_to_self_report_html: "%{name} назначил(а) себя для решения жалобы %{target}" + change_email_user_html: "%{name} изменил адрес электронной почты пользователя %{target}" change_role_user_html: "%{name} изменил(а) роль %{target}" + confirm_user_html: "%{name} подтвержденный адрес электронной почты пользователя %{target}" create_account_warning_html: "%{name} выдал(а) предупреждение %{target}" create_announcement_html: "%{name} создал(а) новое объявление %{target}" + create_canonical_email_block_html: "%{name} заблокировал письмо с хэшем %{target}" create_custom_emoji_html: "%{name} загрузил(а) новый эмодзи %{target}" create_domain_allow_html: "%{name} разрешил(а) федерацию с доменом %{target}" create_domain_block_html: "%{name} заблокировал(а) домен %{target}" + create_email_domain_block_html: "%{name} заблокированный почтовый домен %{target}" create_ip_block_html: "%{name} создал(а) правило для IP %{target}" create_unavailable_domain_html: "%{name} приостановил доставку на узел %{target}" create_user_role_html: "%{name} создал(а) роль %{target}" demote_user_html: "%{name} разжаловал(а) пользователя %{target}" destroy_announcement_html: "%{name} удалил(а) объявление %{target}" + destroy_canonical_email_block_html: "%{name} разблокированное письмо с хэшем %{target}" destroy_custom_emoji_html: "%{name} удалил(а) эмодзи %{target}" destroy_domain_allow_html: "%{name} запретил(а) федерацию с доменом %{target}" destroy_domain_block_html: "%{name} снял(а) блокировку с домена %{target}" + destroy_email_domain_block_html: "%{name} разблокированный почтовый домен %{target}" destroy_instance_html: "%{name} очистил(а) данные для домена %{target}" destroy_ip_block_html: "%{name} удалил(а) правило для IP %{target}" destroy_status_html: "%{name} удалил(а) пост пользователя %{target}" @@ -250,8 +268,10 @@ ru: destroy_user_role_html: "%{name} удалил(а) роль %{target}" disable_2fa_user_html: "%{name} отключил(а) требование двухэтапной авторизации для пользователя %{target}" disable_custom_emoji_html: "%{name} отключил(а) эмодзи %{target}" + disable_sign_in_token_auth_user_html: "%{name} отключил аутентификацию по маркеру электронной почты для %{target}" disable_user_html: "%{name} заморозил(а) пользователя %{target}" enable_custom_emoji_html: "%{name} включил(а) эмодзи %{target}" + enable_sign_in_token_auth_user_html: "%{name} включил аутентификацию с помощью маркера электронной почты для %{target}" enable_user_html: "%{name} разморозил(а) пользователя %{target}" memorialize_account_html: "%{name} перевел(а) учётную запись пользователя %{target} в статус памятника" promote_user_html: "%{name} повысил(а) пользователя %{target}" @@ -259,6 +279,7 @@ ru: reject_user_html: "%{name} отклонил(а) регистрацию %{target}" remove_avatar_user_html: "%{name} убрал(а) аватарку пользователя %{target}" reopen_report_html: "%{name} повторно открыл(а) жалобу %{target}" + resend_user_html: "%{name} повторно отправил письмо с подтверждением для %{target}" reset_password_user_html: "%{name} сбросил(а) пароль пользователя %{target}" resolve_report_html: "%{name} решил(а) жалобу %{target}" sensitive_account_html: "%{name} установил(а) отметку файлов %{target} как «деликатного характера»" @@ -273,6 +294,7 @@ ru: update_custom_emoji_html: "%{name} обновил(а) эмодзи %{target}" update_domain_block_html: "%{name} обновил(а) блокировку домена для %{target}" update_ip_block_html: "%{name} изменил(а) правило для IP %{target}" + update_report_html: "%{name} обновленный отчет %{target}" update_status_html: "%{name} изменил(а) пост пользователя %{target}" update_user_role_html: "%{name} изменил(а) роль %{target}" deleted_account: удалённая учётная запись @@ -437,6 +459,8 @@ ru: new: create: Создать блокировку resolve: Проверить домен + title: Блокировка нового почтового домена + no_email_domain_block_selected: Блоки почтовых доменов не были изменены, так как ни один из них не был выбран not_permitted: Не разрешено resolved_through_html: Разрешено через %{domain} title: Заблокированные e-mail домены @@ -464,6 +488,9 @@ ru: title: Рекомендации подписок unsuppress: Восстановить рекомендацию instances: + audit_log: + title: Последние журналы аудита + view_all: Просмотр полных журналов аудита availability: description_html: few: Если доставка в домен завершается сбоем %{count} дня, дальнейшие попытки доставки предприниматься не будут, пока не будет получена доставка из домена. @@ -599,6 +626,7 @@ ru: silence_description_html: Учетная запись будет видна только тем пользователям, которые уже подписаны на неё, либо открыли его вручную. Это действие можно отменить в любой момент, и отменяет все жалобы против аккаунта. suspend_description_html: Аккаунт и все его содержимое будут недоступны и в конечном итоге удалены, и взаимодействие с ним будет невозможно. Это действие можно отменить в течение 30 дней. Отменяет все жалобы против этого аккаунта. actions_description_remote_html: Решите вопрос о том, какие меры необходимо принять для урегулирования этой жалобы. Это повлияет только на то, как ваш сервер взаимодействует с этим удаленным аккаунтом и обрабатывает его содержимое. + actions_no_posts: У этого отчета нет связанных с ним сообщений для удаления add_to_report: Прикрепить ещё already_suspended_badges: local: На этом сервере уже забанен @@ -639,6 +667,7 @@ ru: report: Жалоба №%{id} reported_account: Учётная запись нарушителя reported_by: Отправитель жалобы + reported_with_application: Сообщается с приложением resolved: Решённые resolved_msg: Жалоба обработана, спасибо! skip_to_actions: Перейти к действиям @@ -661,6 +690,7 @@ ru: delete_data_html: Удалить профиль и контент @%{acct} через 30 дней, если за это время они не будут разблокированы preview_preamble_html: "@%{acct} получит предупреждение со следующим содержанием:" record_strike_html: Запишите замечание против @%{acct}, чтобы помочь вам в решении будущих нарушений с этого аккаунта + send_email_html: Отправить @%{acct} предупреждающее письмо warning_placeholder: Необязательное дополнительное обоснование действия модерации. target_origin: Происхождение объекта жалобы title: Жалобы @@ -704,6 +734,7 @@ ru: manage_appeals: Управление апелляциями manage_appeals_description: Позволяет пользователям просматривать апелляции на действия модерации manage_blocks: Управление блоками + manage_blocks_description: Позволить пользователям блокировать провайдеров электронной почты и IP-адреса manage_custom_emojis: Управление смайлами manage_custom_emojis_description: Позволяет пользователям управлять пользовательскими эмодзи на сервере manage_federation: Управление Федерацией @@ -776,6 +807,7 @@ ru: disabled: Никому users: Залогиненным локальным пользователям registrations: + moderation_recommandation: Убедитесь, что у вас есть адекватная и оперативная команда модераторов, прежде чем открывать регистрацию для всех желающих! preamble: Контролируйте, кто может создать учетную запись на вашем сервере. title: Регистрации registrations_mode: @@ -783,6 +815,7 @@ ru: approved: Для регистрации требуется подтверждение none: Никто не может регистрироваться open: Все могут регистрироваться + warning_hint: Мы рекомендуем использовать "Требуется одобрение для регистрации", если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и вредоносными регистрациями. security: authorized_fetch: Требовать аутентификацию от федеративных серверов authorized_fetch_hint: Требование аутентификации от федеративных серверов позволяет более строго соблюдать блокировки как на уровне пользователя, так и на уровне сервера. Однако при этом снижается производительность, уменьшается охват ваших ответов и могут возникнуть проблемы совместимости с некоторыми федеративными сервисами. Кроме того, это не помешает специальным исполнителям получать ваши публичные сообщения и учётные записи. @@ -794,6 +827,7 @@ ru: destroyed_msg: Файл успешно удалён. software_updates: critical_update: Критично — пожалуйста, обновите как можно скорее + description: Рекомендуется поддерживать установку Mastodon в актуальном состоянии, чтобы воспользоваться последними исправлениями и функциями. Кроме того, иногда очень важно своевременно обновлять Mastodon, чтобы избежать проблем с безопасностью. По этим причинам Mastodon проверяет наличие обновлений каждые 30 минут и уведомляет вас об этом в соответствии с вашими предпочтениями в отношении уведомлений по электронной почте. documentation_link: Узнать больше release_notes: Примечания к выпуску title: Доступные обновления @@ -881,17 +915,38 @@ ru: message_html: "Ваше хранилище объектов неправильно настроено. Безопасность ваших пользователей находится под угрозой" tags: moderation: + not_trendable: Не в тренде + not_usable: Невозможно использовать + pending_review: В ожидании обзора + review_requested: Обзор запрошен + reviewed: Рассмотрено title: Статус + trendable: Трендовый + unreviewed: Без рецензии + usable: Полезное + name: Название + newest: Новейший + oldest: Старейший + open: Посмотреть публично + reset: Сброс review: Состояние проверки + search: Поиск + title: Хэштеги updated_msg: Настройки хэштега обновлены title: Администрирование trends: allow: Разрешить approved: Принятые + confirm_allow: Вы уверены, что хотите разрешить выбранные теги? + confirm_disallow: Вы уверены, что хотите запретить выбранные теги? disallow: Отклонить links: allow: Разрешить ссылку allow_provider: Разрешить издание + confirm_allow: Вы уверены, что хотите разрешить выбранные ссылки? + confirm_allow_provider: Вы уверены, что хотите разрешить выбранных провайдеров? + confirm_disallow: Вы уверены, что хотите запретить выбранные ссылки? + confirm_disallow_provider: Вы уверены, что хотите запретить выбранных поставщиков? description_html: Это ссылки, которыми в настоящее время много пользуются аккаунты, с которых ваш сервер видит сообщения. Это может помочь вашим пользователям узнать, что происходит в мире. Никакие ссылки не отображаются публично, пока вы не одобрите издателя. Вы также можете разрешить или отклонить индивидуальные ссылки. disallow: Запретить ссылку disallow_provider: Отклонить издание @@ -917,6 +972,10 @@ ru: statuses: allow: Разрешить пост allow_account: Разрешить автора + confirm_allow: Вы уверены, что хотите разрешить выбранные статусы? + confirm_allow_account: Вы уверены, что хотите разрешить выбранные учетные записи? + confirm_disallow: Вы уверены, что хотите запретить выбранные статусы? + confirm_disallow_account: Вы уверены, что хотите запретить выбранные учетные записи? description_html: Это посты, которыми на вашем сервере в данный момент часто делятся и предпочитают, что может помочь вашим новым и постоянным пользователям найти больше людей, чтобы на них подписаться. Посты не будут отображаться публично, пока вы не одобрите автора, а автор не разрешит предлагать его аккаунт другим. Вы также можете разрешить или отклонить отдельные сообщения. disallow: Запретить пост disallow_account: Запретить автора @@ -953,12 +1012,14 @@ ru: many: За последнюю неделю использовало %{count} человек one: За последнюю неделю использовал один человек other: За последнюю неделю использовал %{count} человек + title: Рекомендации и тенденции trending: Популярное warning_presets: add_new: Добавить delete: Удалить edit_preset: Удалить шаблон предупреждения empty: Вы еще не определили пресеты предупреждений. + title: Предупреждающие пред установки webhooks: add_new: Добавить конечную точку delete: Удалить @@ -982,6 +1043,9 @@ ru: title: Вебхуки webhook: Вебхук admin_mailer: + auto_close_registrations: + body: В связи с отсутствием активности модераторов в последнее время, регистрация на %{instance} была автоматически переведена в режим, требующий ручной проверки, чтобы предотвратить использование %{instance} в качестве платформы для потенциальных плохих игроков. Вы можете в любой момент переключить его обратно на открытые регистрации. + subject: Регистрации для %{instance} были автоматически переведены в разряд требующих одобрения new_appeal: actions: delete_statuses: удалить их посты @@ -1035,7 +1099,9 @@ ru: guide_link_text: Каждый может внести свой вклад. sensitive_content: Содержимое деликатного характера application_mailer: + notification_preferences: Изменение предпочтений электронной почты salutation: "%{name}," + settings: 'Измените настройки электронной почты: %{link}' unsubscribe: Отписаться view: 'Просмотр:' view_profile: Просмотреть профиль @@ -1055,6 +1121,7 @@ ru: hint_html: Еще одна вещь! Нам нужно подтвердить, что вы человек (так что мы можем держать спам!). Решите капчу ниже и нажмите кнопку «Продолжить». title: Проверка безопасности confirmations: + awaiting_review: Ваш адрес электронной почты подтвержден! Сотрудники %{domain} сейчас рассматривают вашу регистрацию. Вы получите письмо, если они одобрят вашу учетную запись! awaiting_review_title: Ваша регистрация проверяется clicking_this_link: нажатие на эту ссылку login_link: войти @@ -1062,6 +1129,7 @@ ru: redirect_to_app_html: Вы должны были перенаправлены на приложение %{app_name}. Если этого не произошло, попробуйте %{clicking_this_link} или вернитесь к приложению вручную. registration_complete: Ваша регистрация на %{domain} завершена! welcome_title: Добро пожаловать, %{name}! + wrong_email_hint: Если этот адрес электронной почты неверен, вы можете изменить его в настройках аккаунта. delete_account: Удалить учётную запись delete_account_html: Удалить свою учётную запись можно в два счёта здесь, но прежде у вас будет спрошено подтверждение. description: @@ -1082,6 +1150,7 @@ ru: or_log_in_with: Или войти с помощью privacy_policy_agreement_html: Мной прочитана и принята политика конфиденциальности progress: + confirm: Подтвердите электронную почту details: Ваши данные review: Наш обзор rules: Принять правила @@ -1103,8 +1172,10 @@ ru: security: Безопасность set_new_password: Задать новый пароль setup: + email_below_hint_html: Проверьте папку "Спам" или запросите другую. Вы можете исправить свой адрес электронной почты, если он неправильный. email_settings_hint_html: Нажмите на ссылку, которую мы отправили вам для проверки %{email}. Мы будем ждать прямо здесь. link_not_received: Не получили ссылку? + new_confirmation_instructions_sent: Через несколько минут вы получите новое письмо со ссылкой для подтверждения! title: Проверьте свой почтовый ящик sign_in: preamble_html: Войдите, используя ваши учётные данные %{domain}. Если ваша учётная запись размещена на другом сервере, вы не сможете здесь войти. @@ -1115,12 +1186,20 @@ ru: title: Зарегистрируйтесь в %{domain}. status: account_status: Статус учётной записи + confirming: Жду подтверждения по электронной почте. functional: Ваша учётная запись в полном порядке. + pending: Ваша заявка находится на рассмотрении у наших сотрудников. Это может занять некоторое время. Вы получите электронное письмо, если ваша заявка будет одобрена. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи. view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи too_fast: Форма отправлена слишком быстро, попробуйте еще раз. use_security_key: Использовать ключ безопасности + author_attribution: + example_title: Образец текста + hint_html: Контролируйте, как вы будете отмечены при обмене ссылками на Mastodon. + more_from_html: Больше от %{name} + s_blog: "%{name}'S Блог" + title: Авторская атрибуция challenge: confirm: Продолжить hint_html: "Подсказка: мы не будем спрашивать пароль повторно в течение часа." @@ -1129,7 +1208,6 @@ ru: crypto: errors: invalid_key: не является допустимым Ed25519 или Curve25519 ключом - invalid_signature: не является допустимой Ed25519 подписью date: formats: default: "%d %b %Y" @@ -1158,6 +1236,9 @@ ru: before: 'Внимательно прочитайте следующую информацию перед началом:' caches: Некоторые данные, обработанные другими узлами, однако, могут храниться ещё какое-то время data_removal: Все ваши золотые посты, шикарный профиль и прочие данные будут безвозвратно уничтожены + email_change_html: Вы можете изменить свой адрес электронной почты, не удаляя свою учетную запись + email_contact_html: Если оно все еще не пришло, вы можете обратиться за помощью по электронной почте %{email} + email_reconfirmation_html: Если вы не получили подтверждение по электронной почте, вы можете запросить его снова irreversible: После удаления восстановить или повторно активировать учётную запись не получится more_details_html: За всеми подробностями, изучите политику конфиденциальности. username_available: Ваше имя пользователя снова станет доступным @@ -1404,6 +1485,7 @@ ru: authentication_methods: otp: приложение двухфакторной аутентификации password: пароль + sign_in_token: код безопасности электронной почты webauthn: ключи безопасности description_html: Если вы видите неопознанное действие, смените пароль и/или включите двухфакторную авторизацию. empty: Нет доступной истории входов @@ -1414,10 +1496,20 @@ ru: unsubscribe: action: Да, отписаться complete: Подписка отменена + emails: + notification_emails: + favourite: любимые электронные письма с уведомлениями + follow: Следить за электронными сообщениями + follow_request: Письма с просьбой о помощи + mention: Упоминание электронных писем с уведомлениями + reblog: Уведомления по электронной почте + resubscribe_html: Если вы отписались от рассылки по ошибке, вы можете повторно подписаться на рассылку в настройках настроек почтовых уведомлений. + success_html: Вы больше не будете получать %{type} для Mastodon на %{domain} на вашу электронную почту %{email}. title: Отписаться media_attachments: validations: images_and_video: Нельзя добавить видео к посту с изображениями + not_found: Медиа %{ids} не найдено или уже прикреплено к другому сообщению not_ready: Не удаётся прикрепить файлы, обработка которых не завершена. Повторите попытку чуть позже! too_many: Нельзя добавить более 4 файлов migrations: @@ -1494,6 +1586,8 @@ ru: update: subject: "%{name} изменил(а) пост" notifications: + administration_emails: Уведомления администратора по электронной почте + email_events: События для уведомлений по электронной почте email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:' number: human: @@ -1652,16 +1746,26 @@ ru: import: Импорт import_and_export: Импорт и экспорт migrate: Миграция учётной записи + notifications: E-mail уведомление preferences: Настройки profile: Профиль relationships: Подписки и подписчики + severed_relationships: Разорванные отношения statuses_cleanup: Авто-удаление постов strikes: Замечания модерации two_factor_authentication: Подтверждение входа webauthn_authentication: Ключи безопасности severed_relationships: + download: Скачать (%{count}) event_type: + account_suspension: Приостановка аккаунта (%{target_name}) + domain_block: Приостановка сервера (%{target_name}) user_domain_block: Вы заблокировали %{target_name} + lost_followers: Потерянные подписчики + lost_follows: Потерянный следует + preamble: Вы можете потерять подписчиков и последователей, если заблокируете домен или, если ваши модераторы решат приостановить работу удаленного сервера. Когда это произойдет, вы сможете загрузить списки разорванных отношений, чтобы проверить их и, возможно, импортировать на другой сервер. + purged: Информация об этом сервере была удалена администраторами вашего сервера. + type: Событие statuses: attached: audio: @@ -1752,6 +1856,7 @@ ru: contrast: Mastodon (высококонтрастная) default: Mastodon (тёмная) mastodon-light: Mastodon (светлая) + system: Автоматически (используйте системную тему) time: formats: default: "%d %b %Y, %H:%M" @@ -1840,8 +1945,45 @@ ru: suspend: Учётная запись заблокирована welcome: apps_android_action: Скачать на Google Play + apps_ios_action: Скачать в App Store + apps_step: Загрузите наши официальные приложения. + apps_title: Приложения Mastodon + checklist_subtitle: 'Давайте начнем знакомство с этим новым социальным рубежом:' + checklist_title: Приветственный контрольный список + edit_profile_action: Персонализация + edit_profile_step: Усильте взаимодействие, заполнив полный профиль. + edit_profile_title: Персонализируйте свой профиль explanation: Вот несколько советов для новичков feature_action: Подробнее + feature_audience: Mastodon предоставляет вам уникальную возможность управлять своей аудиторией без посредников. Mastodon, развернутый на вашей собственной инфраструктуре, позволяет вам следить и быть преследуемым с любого другого сервера Mastodon в Интернете и не контролируется никем, кроме вас. + feature_audience_title: Создайте уверенную аудиторию + feature_control: Вы сами знаете, что хотите видеть в своей ленте. Никаких алгоритмов или рекламы, чтобы тратить ваше время. Следите за любым человеком на любом сервере Mastodon с одного аккаунта и получайте его сообщения в хронологическом порядке, а также сделайте свой уголок интернета немного больше похожим на себя. + feature_control_title: Контролируйте свой график + feature_creativity: Mastodon поддерживает аудио-, видео- и фотопосты, описания доступности, опросы, предупреждения о содержании, анимированные аватары, пользовательские emojis, управление обрезкой миниатюр и многое другое, чтобы помочь вам выразить себя в Интернете. Публикуете ли вы свои работы, музыку или подкаст, Mastodon всегда готов помочь вам. + feature_creativity_title: Непревзойденная креативность + feature_moderation: Mastodon возвращает принятие решений в ваши руки. Каждый сервер создает свои собственные правила и нормы, которые соблюдаются локально, а не сверху вниз, как в корпоративных социальных сетях, что позволяет наиболее гибко реагировать на потребности различных групп людей. Присоединяйтесь к серверу с правилами, с которыми вы согласны, или создайте свой собственный. + feature_moderation_title: Модерирование, каким оно должно быть + follow_action: Следуйте за + follow_step: Следить за интересными людьми - вот что такое Mastodon. + follow_title: Персонализируйте свою домашнюю ленту + follows_subtitle: Следите за известными аккаунтами + follows_title: За кем следить + follows_view_more: Посмотреть больше людей, за которыми стоит следить + hashtags_recent_count: + few: "%{people} человека за последние 2 дня" + many: "%{people} человек за последние 2 дня" + one: "%{people} человек за последние 2 дня" + other: "%{people} человек за последние 2 дня" + hashtags_subtitle: Изучите, что было в тренде за последние 2 дня + hashtags_title: Модные хэштеги + hashtags_view_more: Посмотреть другие трендовые хэштеги + post_action: Составить + post_step: Поприветствуйте мир с помощью текста, фотографий, видео или опросов. + post_title: Сделайте свой первый пост + share_action: Поделиться + share_step: Пусть ваши друзья знают, как найти вас на Mastodon. + share_title: Поделитесь информацией о компании Mastodon + sign_in_action: Зарегистрироваться subject: Добро пожаловать в Mastodon title: Добро пожаловать на борт, %{name}! users: @@ -1850,6 +1992,7 @@ ru: invalid_otp_token: Введен неверный код двухфакторной аутентификации otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email} rate_limited: Слишком много попыток аутентификации, повторите попытку позже. + seamless_external_login: Вы вошли в систему через внешнюю службу, поэтому настройки пароля и электронной почты недоступны. signed_in_as: 'Выполнен вход под именем:' verification: extra_instructions_html: Подсказка: Ссылка на вашем сайте может быть невидимой. Важной частью является rel="me", который предотвращает выдачу себя за другое лицо на сайтах с пользовательским контентом. Вы даже можете использовать тег link в заголовке страницы вместо a, но HTML должен быть доступен без выполнения JavaScript. @@ -1858,6 +2001,7 @@ ru: instructions_html: Скопируйте и вставьте код ниже в HTML вашего сайта. Затем, добавьте адрес вашего веб сайта в одно из дополнительных полей на вкладке "Редактировать профиль" и сохраните изменения. verification: Верификация ссылок verified_links: Ваши ссылки подтверждения + website_verification: Проверка веб-сайта webauthn_credentials: add: Добавить новый ключ безопасности create: diff --git a/config/locales/sc.yml b/config/locales/sc.yml index 435749f470..780270ddf1 100644 --- a/config/locales/sc.yml +++ b/config/locales/sc.yml @@ -693,7 +693,6 @@ sc: crypto: errors: invalid_key: no est una crae Ed25519 o Curve25519 vàlida - invalid_signature: no est una firma Ed25519 vàlida date: formats: default: "%d %b, %Y" diff --git a/config/locales/sco.yml b/config/locales/sco.yml index 70143a968e..97eaa21ed6 100644 --- a/config/locales/sco.yml +++ b/config/locales/sco.yml @@ -940,7 +940,6 @@ sco: crypto: errors: invalid_key: isnae a valid Ed25519 or Curve25519 key - invalid_signature: isnae a valid Ed25519 signature date: formats: default: "%b %d, %Y" diff --git a/config/locales/si.yml b/config/locales/si.yml index 135a99ceb2..8460de01da 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -829,7 +829,6 @@ si: crypto: errors: invalid_key: වලංගු Ed25519 හෝ Curve25519 යතුරක් නොවේ - invalid_signature: වලංගු Ed25519 අත්සනක් නොවේ date: formats: default: "%Y %b %d" diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 2a2d06ce9b..f2108828d5 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -16,12 +16,12 @@ es-MX: account_migration: acct: Especifique el nombre de usuario@dominio de la cuenta a la cual desea migrar account_warning_preset: - text: Puede usar sintaxis de toots, como URLs, hashtags y menciones + text: Puede usar sintaxis de publicaciones, como URLs, etiquetas y menciones title: Opcional. No visible para el destinatario admin_account_action: - include_statuses: El usuario verá qué toots han causado la acción de moderación o advertencia + include_statuses: El usuario verá qué publicaciones han causado la acción de moderación o advertencia send_email_notification: El usuario recibirá una explicación de lo que sucedió con respecto a su cuenta - text_html: Opcional. Puede usar sintaxis de toots. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo + text_html: Opcional. Puede usar sintaxis de publicaciones. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo type_html: Elige qué hacer con %{acct} types: disable: Evitar que el usuario utilice su cuenta, pero no eliminar ni ocultar sus contenidos. @@ -35,7 +35,7 @@ es-MX: ends_at: Opcional. El anuncio desaparecerá automáticamente en este momento scheduled_at: Dejar en blanco para publicar el anuncio inmediatamente starts_at: Opcional. En caso de que su anuncio esté vinculado a un intervalo de tiempo específico - text: Puedes usar la sintaxis toot. Por favor ten en cuenta el espacio que ocupará el anuncio en la pantalla del usuario + text: Puedes usar la sintaxis de publicaciones. Por favor ten en cuenta el espacio que ocupará el anuncio en la pantalla del usuario appeal: text: Sólo puede apelar una amonestación a la vez defaults: @@ -49,12 +49,12 @@ es-MX: email: Se le enviará un correo de confirmación header: WEBP, PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px inbox_url: Copia la URL de la página principal del relés que quieres utilizar - irreversible: Los toots filtrados desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante + irreversible: Las publicaciones filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante locale: El idioma de la interfaz de usuario, correos y notificaciones push password: Utilice al menos 8 caracteres - phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de un toot + phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. - setting_aggregate_reblogs: No mostrar nuevos retoots para los toots que han sido recientemente retooteados (sólo afecta a los retoots recibidos recientemente) + setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click setting_display_media_default: Ocultar contenido multimedia marcado como sensible @@ -84,7 +84,7 @@ es-MX: closed_registrations_message: Mostrado cuando los registros están cerrados content_cache_retention_period: Todas las publicaciones de otros servidores (incluso impulsos y respuestas) se eliminarán después del número de días especificado, sin tener en cuenta la interacción del usuario local con esos mensajes. Esto incluye mensajes donde un usuario local los ha marcado como marcadores o favoritos. Las menciones privadas entre usuarios de diferentes instancias también se perderán sin posibilidad de recuperación. El uso de esta configuración está destinado a instancias de propósito especial, y rompe muchas expectativas de los usuarios cuando se implementa para un uso de propósito general. custom_css: Puedes aplicar estilos personalizados a la versión web de Mastodon. - favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicon predeterminado de Mastodon con un icono personalizado. + favicon: WEBP, PNG, GIF o JPG. Reemplaza el icono predeterminado de Mastodon con un icono personalizado. mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones creadas por usuarios remotos se almacenan en caché en tu servidor. Cuando se establece un valor positivo, estos archivos se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlaces realizan peticiones a sitios de terceros, se recomienda establecer este valor a al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese momento. peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, sólo que su servidor lo sabe. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general. @@ -130,7 +130,7 @@ es-MX: tag: name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible user: - chosen_languages: Cuando se marca, solo se mostrarán los toots en los idiomas seleccionados en los timelines públicos + chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas role: El rol controla qué permisos tiene el usuario. user_role: color: Color que se utilizará para el rol a lo largo de la interfaz de usuario, como RGB en formato hexadecimal @@ -160,7 +160,7 @@ es-MX: text: Texto predefinido title: Título admin_account_action: - include_statuses: Incluir en el correo electrónico a los toots denunciados + include_statuses: Incluir en el correo electrónico a las publicaciones denunciadas send_email_notification: Notificar al usuario por correo electrónico text: Aviso personalizado type: Acción @@ -205,21 +205,21 @@ es-MX: password: Contraseña phrase: Palabra clave o frase setting_advanced_layout: Habilitar interfaz web avanzada - setting_aggregate_reblogs: Agrupar retoots en las líneas de tiempo + setting_aggregate_reblogs: Agrupar impulsos en las líneas de tiempo setting_always_send_emails: Enviar siempre notificaciones por correo setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot + setting_boost_modal: Mostrar ventana de confirmación antes de impulsar setting_default_language: Idioma de publicación setting_default_privacy: Privacidad de publicaciones setting_default_sensitive: Marcar siempre imágenes como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un toot + setting_delete_modal: Mostrar diálogo de confirmación antes de borrar una publicación setting_disable_hover_cards: Desactivar vista previa del perfil al pasar el cursor setting_disable_swiping: Deshabilitar movimientos de deslizamiento setting_display_media: Visualización multimedia setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo - setting_expand_spoilers: Siempre expandir los toots marcados con advertencias de contenido + setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red setting_reduce_motion: Reducir el movimiento de las animaciones setting_system_font_ui: Utilizar la tipografía por defecto del sistema diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index 68fc629d9e..203b023715 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -20,6 +20,7 @@ kab: irreversible: Tisuffaɣ i tessazedgeḍ ad ttwakksent i lebda, ula ma tekkseḍ imsizdeg-nni ar zdat locale: Tutlayt n ugrudem, imaylen d walɣuten yettudemren password: Seqdec ma drus 8 n yisekkilen + setting_always_send_emails: S umata, ilɣa s yimayl ur d-ttwaceyyεen ara mi ara tesseqdaceḍ Mastodon s wudem urmid setting_display_media_default: Ffer imidyaten yettwacreḍ d infariyen setting_display_media_hide_all: Ffer yal tikkelt akk taywalt setting_display_media_show_all: Ffer yal tikkelt teywalt yettwacreḍ d tanafrit @@ -84,8 +85,9 @@ kab: password: Awal uffir phrase: Awal n tsarut neɣ tafyirt setting_advanced_layout: Rmed agrudem n web leqqayen - setting_default_language: Tutlayt n tira - setting_default_privacy: Tabaḍnit n tira + setting_always_send_emails: Dima ttazen-d ilɣa s yimayl + setting_default_language: Tutlayt n usuffeɣ + setting_default_privacy: Tabaḍnit n usuffeɣ setting_display_media: Askanay n imidyaten setting_display_media_default: Akk-a kan setting_display_media_hide_all: Ffer-iten akk diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index fee07fa5e0..a649b4ec5a 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -3,6 +3,7 @@ ko: simple_form: hints: account: + attribution_domains_as_text: 가짜 기여로부터 보호합니다. discoverable: 내 공개 게시물과 프로필이 마스토돈의 다양한 추천 기능에 나타날 수 있고 프로필이 다른 사용자에게 제안될 수 있습니다 display_name: 진짜 이름 또는 재미난 이름. fields: 홈페이지, 호칭, 나이, 뭐든지 적고 싶은 것들. @@ -143,6 +144,7 @@ ko: url: 이벤트가 어디로 전송될 지 labels: account: + attribution_domains_as_text: 특정 웹사이트만 허용하기 discoverable: 발견하기 알고리즘에 프로필과 게시물을 추천하기 fields: name: 라벨 diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 4369c3c42b..ed27e08bc3 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -3,7 +3,7 @@ lv: simple_form: hints: account: - attribution_domains_as_text: Aizsargā no nepatiesas piedēvēšanas. + attribution_domains_as_text: Aizsargā no nepatiesa attiecinājuma. discoverable: Tavas publiskās ziņas un profils var tikt piedāvāti vai ieteikti dažādās Mastodon vietās, un tavs profils var tikt ieteikts citiem lietotājiem. display_name: Tavs pilnais vārds vai tavs joku vārds. fields: Tava mājas lapa, vietniekvārdi, vecums, viss, ko vēlies. diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index b41457e86a..3ff746451b 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -3,6 +3,7 @@ ru: simple_form: hints: account: + attribution_domains_as_text: Защищает от ложных атрибуций. discoverable: Ваши публичные сообщения и профиль могут быть показаны или рекомендованы в различных разделах Mastodon, и ваш профиль может быть предложен другим пользователям. display_name: Ваше полное имя или псевдоним. fields: Ваша домашняя страница, местоимения, возраст - все, что угодно. @@ -77,11 +78,15 @@ ru: warn: Скрыть отфильтрованный контент за предупреждением с указанием названия фильтра form_admin_settings: activity_api_enabled: Подсчёт количества локальных постов, активных пользователей и новых регистраций на еженедельной основе + app_icon: WEBP, PNG, GIF или JPG. Замените значок приложения по умолчанию на мобильных устройствах пользовательским значком. backups_retention_period: Пользователи могут создавать архивы своих постов, чтобы потом их забрать. Если задать положительное значение, эти архивы автоматически удалятся с вашего хранилища через указанное число дней. bootstrap_timeline_accounts: Эти аккаунты будут рекомендованы для подписки новым пользователям. closed_registrations_message: Отображается, когда регистрация закрыта + content_cache_retention_period: Все сообщения с других серверов (включая бусты и ответы) будут удалены через указанное количество дней, независимо от того, как локальный пользователь взаимодействовал с этими сообщениями. Это касается и тех сообщений, которые локальный пользователь пометил в закладки или избранное. Приватные упоминания между пользователями из разных инстансов также будут потеряны и не смогут быть восстановлены. Использование этой настройки предназначено для экземпляров специального назначения и нарушает многие ожидания пользователей при использовании в общих целях. custom_css: Вы можете применять пользовательские стили в веб-версии Mastodon. + favicon: WEBP, PNG, GIF или JPG. Заменяет стандартный фавикон Mastodon на собственный значок. mascot: Заменяет иллюстрацию в расширенном веб-интерфейсе. + media_cache_retention_period: Медиафайлы из сообщений, сделанных удаленными пользователями, кэшируются на вашем сервере. При положительном значении медиафайлы будут удалены через указанное количество дней. Если медиаданные будут запрошены после удаления, они будут загружены повторно, если исходный контент все еще доступен. В связи с ограничениями на частоту опроса карточек предварительного просмотра ссылок на сторонних сайтах рекомендуется устанавливать значение не менее 14 дней, иначе карточки предварительного просмотра ссылок не будут обновляться по запросу до этого времени. peers_api_enabled: Список доменных имен, с которыми сервер столкнулся в fediverse. Здесь нет данных о том, федерировались ли вы с данным сервером, только что ваш сервер знает об этом. Это используется службами, которые собирают статистику по федерации в общем смысле. profile_directory: В каталоге профилей перечислены все пользователи, которые согласились быть доступными для обнаружения. require_invite_text: Когда регистрация требует ручного одобрения, сделайте текстовый ввод "Почему вы хотите присоединиться?" обязательным, а не опциональным @@ -114,6 +119,7 @@ ru: sign_up_requires_approval: Новые регистрации потребуют вашего одобрения severity: Выберите, что будет происходить с запросами с этого IP rule: + hint: Необязательно. Предоставьте дополнительные сведения о правиле text: Опишите правило или требование для пользователей на этом сервере. Постарайтесь сделать его коротким и простым sessions: otp: 'Введите код двухфакторной аутентификации, сгенерированный в мобильном приложении, или используйте один из ваших кодов восстановления:' @@ -125,6 +131,7 @@ ru: name: Вы можете изменить только регистр букв чтобы, например, сделать тег более читаемым user: chosen_languages: Если выбрано, то в публичных лентах будут показаны только посты на выбранных языках. + role: Роль определяет, какими правами обладает пользователь. user_role: color: Цвет, который будет использоваться для роли в интерфейсе (UI), как RGB в формате HEX highlighted: Это действие сделает роль публичной @@ -137,6 +144,7 @@ ru: url: Куда события будут отправляться labels: account: + attribution_domains_as_text: Разрешить только определенные сайты discoverable: Профиль и сообщения в алгоритмах обнаружения fields: name: Пункт @@ -241,6 +249,7 @@ ru: backups_retention_period: Период хранения архива пользователя bootstrap_timeline_accounts: Всегда рекомендовать эти учетные записи новым пользователям closed_registrations_message: Сообщение, когда регистрация недоступна + content_cache_retention_period: Период хранения удаленного содержимого custom_css: Пользовательский CSS favicon: Favicon mascot: Пользовательский маскот (устаревшее) diff --git a/config/locales/sl.yml b/config/locales/sl.yml index ef6d00b8d3..c8e806bf35 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1196,7 +1196,6 @@ sl: crypto: errors: invalid_key: ni veljaven ključ Ed25519 ali Curve25519 - invalid_signature: ni veljaven podpis Ed25519 date: formats: default: "%d %b %Y" diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 70d20592a5..294c8a888f 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1166,7 +1166,6 @@ sq: crypto: errors: invalid_key: s’është kyç Ed25519 ose Curve25519 i vlefshëm - invalid_signature: s’është nënshkrim Ed25519 i vlefshëm date: formats: default: "%d %b, %Y" diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 91f0933398..bfb52c275b 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -1110,7 +1110,6 @@ sr-Latn: crypto: errors: invalid_key: nije validan Ed25519 ili Curve25519 ključ - invalid_signature: nije validan Ed25519 potpis date: formats: default: "%d. %b. %Y." diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 67aee931be..af7e7ab8d6 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1140,7 +1140,6 @@ sr: crypto: errors: invalid_key: није валидан Ed25519 или Curve25519 кључ - invalid_signature: није валидан Ed25519 потпис date: formats: default: "%d. %b. %Y." diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 99b7ec9b3a..6146cfc8d7 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1128,7 +1128,6 @@ sv: crypto: errors: invalid_key: är inte en giltig Ed25519 eller Curve25519 nyckel - invalid_signature: är inte en giltig Ed25519 signatur date: formats: default: "%b %d, %Y" diff --git a/config/locales/ta.yml b/config/locales/ta.yml index 3a98b6a25d..56b2895c74 100644 --- a/config/locales/ta.yml +++ b/config/locales/ta.yml @@ -188,7 +188,6 @@ ta: crypto: errors: invalid_key: ஒரு முறையான Ed25519 அல்லது Curve25519 key அல்ல - invalid_signature: ஒரு முறையான Ed25519 அடையாளம் அல்ல filters: index: empty: தடுப்புகள் ஏதும் இல்லை. diff --git a/config/locales/th.yml b/config/locales/th.yml index 65424f4eb6..d56385f261 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1156,7 +1156,6 @@ th: crypto: errors: invalid_key: ไม่ใช่กุญแจ Ed25519 หรือ Curve25519 ที่ถูกต้อง - invalid_signature: ไม่ใช่ลายเซ็น Ed25519 ที่ถูกต้อง date: formats: default: "%d %b %Y" diff --git a/config/locales/tr.yml b/config/locales/tr.yml index d6ca6b4276..16dd4c899d 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1174,7 +1174,6 @@ tr: crypto: errors: invalid_key: geçerli bir Ed25519 veya Curve25519 anahtarı değil - invalid_signature: geçerli bir Ed25519 imzası değil date: formats: default: "%d %b %Y" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index e8c4e68998..0ef08a1555 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1210,7 +1210,6 @@ uk: crypto: errors: invalid_key: не є припустимим ключем Ed25519 або Curve25519 - invalid_signature: не є дійсним підписом Ed25519 date: formats: default: "%b %d, %Y" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 8f047a2cb7..98696aef7c 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1156,7 +1156,6 @@ vi: crypto: errors: invalid_key: không phải là mã khóa Ed25519 hoặc Curve25519 đúng - invalid_signature: không phải là chữ ký số Ed25519 đúng date: formats: default: "%-d %B, %Y" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 277785f683..8b34da076a 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1156,7 +1156,6 @@ zh-CN: crypto: errors: invalid_key: 不是有效的 Ed25519 或者 Curve25519 密钥 - invalid_signature: 不是有效的 Ed25519 签名 date: formats: default: "%Y年%m月%d日" diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 7682712759..598c65d049 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1071,7 +1071,6 @@ zh-HK: crypto: errors: invalid_key: 不是一個有效的 Ed25519 或 Curve25519 密鑰 - invalid_signature: 不是一個有效的 Ed25519 簽名 date: formats: default: "%Y年%b月%d日" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 35f000b601..41c4c8a534 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1158,7 +1158,6 @@ zh-TW: crypto: errors: invalid_key: 這不是一把有效的 Ed25519 或 Curve25519 金鑰 - invalid_signature: 這不是有效的 Ed25519 簽章 date: formats: default: "%Y年%b月%d日" diff --git a/config/routes.rb b/config/routes.rb index 242ca06262..86248dda41 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -135,7 +135,6 @@ Rails.application.routes.draw do scope module: :activitypub do resource :outbox, only: [:show] resource :inbox, only: [:create] - resource :claim, only: [:create] resources :collections, only: [:show] resource :followers_synchronization, only: [:show] end diff --git a/config/routes/api.rb b/config/routes/api.rb index 4d422de373..48b35cc154 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -69,23 +69,6 @@ namespace :api, format: false do end end - # namespace :crypto do - # resources :deliveries, only: :create - - # namespace :keys do - # resource :upload, only: [:create] - # resource :query, only: [:create] - # resource :claim, only: [:create] - # resource :count, only: [:show] - # end - - # resources :encrypted_messages, only: [:index] do - # collection do - # post :clear - # end - # end - # end - resources :conversations, only: [:index, :destroy] do member do post :read diff --git a/db/post_migrate/20240720140205_drop_end_to_end_message_tables.rb b/db/post_migrate/20240720140205_drop_end_to_end_message_tables.rb new file mode 100644 index 0000000000..dd5662885c --- /dev/null +++ b/db/post_migrate/20240720140205_drop_end_to_end_message_tables.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class DropEndToEndMessageTables < ActiveRecord::Migration[7.1] + def up + drop_table :system_keys + drop_table :one_time_keys + drop_table :encrypted_messages + drop_table :devices + safety_assured { remove_column :accounts, :devices_url } + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/post_migrate/20240916190140_remove_crypto_scope_values.rb b/db/post_migrate/20240916190140_remove_crypto_scope_values.rb new file mode 100644 index 0000000000..8caf5b801d --- /dev/null +++ b/db/post_migrate/20240916190140_remove_crypto_scope_values.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class RemoveCryptoScopeValues < ActiveRecord::Migration[7.1] + def up + applications.in_batches do |records| + records.update_all(<<~SQL.squish) + scopes = TRIM(REPLACE(scopes, 'crypto', '')) + SQL + end + + tokens.in_batches do |records| + records.update_all(<<~SQL.squish) + scopes = TRIM(REPLACE(scopes, 'crypto', '')) + SQL + end + end + + def down + raise ActiveRecord::IrreversibleMigration + end + + private + + def applications + Doorkeeper::Application + .where("scopes LIKE '%crypto%'") + end + + def tokens + Doorkeeper::AccessToken + .where("scopes LIKE '%crypto%'") + end +end diff --git a/db/schema.rb b/db/schema.rb index 4c32e2a9da..332854a05a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do +ActiveRecord::Schema[7.1].define(version: 2024_09_16_190140) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -193,7 +193,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do t.boolean "hide_collections" t.integer "avatar_storage_schema_version" t.integer "header_storage_schema_version" - t.string "devices_url" t.datetime "sensitized_at", precision: nil t.integer "suspension_origin" t.boolean "trendable" @@ -412,19 +411,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do t.index ["account_id"], name: "index_custom_filters_on_account_id" end - create_table "devices", force: :cascade do |t| - t.bigint "access_token_id" - t.bigint "account_id" - t.string "device_id", default: "", null: false - t.string "name", default: "", null: false - t.text "fingerprint_key", default: "", null: false - t.text "identity_key", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["access_token_id"], name: "index_devices_on_access_token_id" - t.index ["account_id"], name: "index_devices_on_account_id" - end - create_table "domain_allows", force: :cascade do |t| t.string "domain", default: "", null: false t.datetime "created_at", precision: nil, null: false @@ -454,20 +440,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end - create_table "encrypted_messages", id: :bigint, default: -> { "timestamp_id('encrypted_messages'::text)" }, force: :cascade do |t| - t.bigint "device_id" - t.bigint "from_account_id" - t.string "from_device_id", default: "", null: false - t.integer "type", default: 0, null: false - t.text "body", default: "", null: false - t.text "digest", default: "", null: false - t.text "message_franking", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["device_id"], name: "index_encrypted_messages_on_device_id" - t.index ["from_account_id"], name: "index_encrypted_messages_on_from_account_id" - end - create_table "favourites", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false @@ -781,17 +753,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end - create_table "one_time_keys", force: :cascade do |t| - t.bigint "device_id" - t.string "key_id", default: "", null: false - t.text "key", default: "", null: false - t.text "signature", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["device_id"], name: "index_one_time_keys_on_device_id" - t.index ["key_id"], name: "index_one_time_keys_on_key_id" - end - create_table "pghero_space_stats", force: :cascade do |t| t.text "database" t.text "schema" @@ -1107,12 +1068,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do t.index ["status_id"], name: "index_statuses_tags_on_status_id" end - create_table "system_keys", force: :cascade do |t| - t.binary "key" - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - end - create_table "tag_follows", force: :cascade do |t| t.bigint "tag_id", null: false t.bigint "account_id", null: false @@ -1309,11 +1264,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do add_foreign_key "custom_filter_statuses", "custom_filters", on_delete: :cascade add_foreign_key "custom_filter_statuses", "statuses", on_delete: :cascade add_foreign_key "custom_filters", "accounts", on_delete: :cascade - add_foreign_key "devices", "accounts", on_delete: :cascade - add_foreign_key "devices", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade - add_foreign_key "encrypted_messages", "accounts", column: "from_account_id", on_delete: :cascade - add_foreign_key "encrypted_messages", "devices", on_delete: :cascade add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade add_foreign_key "featured_tags", "accounts", on_delete: :cascade @@ -1356,7 +1307,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_09_09_014637) do add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id", name: "fk_f5fc4c1ee3", on_delete: :cascade add_foreign_key "oauth_access_tokens", "users", column: "resource_owner_id", name: "fk_e84df68546", on_delete: :cascade add_foreign_key "oauth_applications", "users", column: "owner_id", name: "fk_b0988c7c0a", on_delete: :cascade - add_foreign_key "one_time_keys", "devices", on_delete: :cascade add_foreign_key "poll_votes", "accounts", on_delete: :cascade add_foreign_key "poll_votes", "polls", on_delete: :cascade add_foreign_key "polls", "accounts", on_delete: :cascade diff --git a/spec/controllers/activitypub/claims_controller_spec.rb b/spec/controllers/activitypub/claims_controller_spec.rb deleted file mode 100644 index e887be2cbe..0000000000 --- a/spec/controllers/activitypub/claims_controller_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::ClaimsController do - let(:account) { Fabricate(:account) } - - describe 'POST #create' do - context 'without signature' do - before do - post :create, params: { account_username: account.username }, body: '{}' - end - - it 'returns http not authorized' do - expect(response).to have_http_status(401) - end - end - end -end diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 3cc3460718..713ea3ff16 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -412,44 +412,4 @@ RSpec.describe Auth::SessionsController do end end end - - describe 'GET #webauthn_options' do - subject { get :webauthn_options, session: { attempt_user_id: user.id } } - - let!(:user) do - Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) - end - - context 'with WebAuthn and OTP enabled as second factor' do - let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } - - let(:fake_client) { WebAuthn::FakeClient.new(domain) } - - before do - user.update(webauthn_id: WebAuthn.generate_user_id) - public_key_credential = WebAuthn::Credential.from_create(fake_client.create) - user.webauthn_credentials.create( - nickname: 'SecurityKeyNickname', - external_id: public_key_credential.id, - public_key: public_key_credential.public_key, - sign_count: '1000' - ) - post :create, params: { user: { email: user.email, password: user.password } } - end - - it 'returns http success' do - subject - - expect(response).to have_http_status 200 - end - end - - context 'when WebAuthn not enabled' do - it 'returns http unauthorized' do - subject - - expect(response).to have_http_status 401 - end - end - end end diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb deleted file mode 100644 index 1eafabc7e5..0000000000 --- a/spec/controllers/settings/exports_controller_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Settings::ExportsController do - render_views - - describe 'GET #show' do - context 'when signed in' do - let(:user) { Fabricate(:user) } - - before do - sign_in user, scope: :user - get :show - end - - it 'returns http success with private cache control headers', :aggregate_failures do - expect(response).to have_http_status(200) - expect(response.headers['Cache-Control']).to include('private, no-store') - end - end - - context 'when not signed in' do - it 'redirects' do - get :show - expect(response).to redirect_to '/auth/sign_in' - end - end - end - - describe 'POST #create' do - before do - sign_in Fabricate(:user), scope: :user - end - - it 'redirects to settings_export_path' do - post :create - expect(response).to redirect_to(settings_export_path) - end - - it 'queues BackupWorker job by 1' do - expect do - post :create - end.to change(BackupWorker.jobs, :size).by(1) - end - end -end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 5042523df8..d9702251f4 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -63,7 +63,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('public'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -79,7 +79,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -168,7 +168,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -184,7 +184,7 @@ RSpec.describe StatusesController do 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -212,7 +212,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -228,7 +228,7 @@ RSpec.describe StatusesController do 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -278,7 +278,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -294,7 +294,7 @@ RSpec.describe StatusesController do 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -370,7 +370,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -385,7 +385,7 @@ RSpec.describe StatusesController do .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') expect(response.headers).to include( 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -412,7 +412,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -428,7 +428,7 @@ RSpec.describe StatusesController do 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) @@ -479,7 +479,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.body).to include status.text end @@ -495,7 +495,7 @@ RSpec.describe StatusesController do 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('private'), 'Content-Type' => include('application/activity+json'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) expect(response.parsed_body) .to include(content: include(status.text)) @@ -779,7 +779,7 @@ RSpec.describe StatusesController do expect(response.headers).to include( 'Vary' => 'Accept, Accept-Language, Cookie', 'Cache-Control' => include('public'), - 'Link' => satisfy { |header| header.to_s.include?('activity+json') } + 'Link' => include('activity+json') ) end end diff --git a/spec/fabricators/device_fabricator.rb b/spec/fabricators/device_fabricator.rb deleted file mode 100644 index 37a2e8977d..0000000000 --- a/spec/fabricators/device_fabricator.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:device) do - access_token { Fabricate.build(:access_token) } - account { Fabricate.build(:account) } - device_id { Faker::Number.number(digits: 5) } - name { Faker::App.name } - fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } - identity_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } -end diff --git a/spec/fabricators/encrypted_message_fabricator.rb b/spec/fabricators/encrypted_message_fabricator.rb deleted file mode 100644 index 349b659c2f..0000000000 --- a/spec/fabricators/encrypted_message_fabricator.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:encrypted_message) do - device { Fabricate.build(:device) } - from_account { Fabricate.build(:account) } - from_device_id { Faker::Number.number(digits: 5) } -end diff --git a/spec/fabricators/one_time_key_fabricator.rb b/spec/fabricators/one_time_key_fabricator.rb deleted file mode 100644 index 505282e05d..0000000000 --- a/spec/fabricators/one_time_key_fabricator.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:one_time_key) do - device { Fabricate.build(:device) } - key_id { Faker::Alphanumeric.alphanumeric(number: 10) } - key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } - - signature do |attrs| - signing_key = Ed25519::SigningKey.generate - attrs[:device].update(fingerprint_key: Base64.strict_encode64(signing_key.verify_key.to_bytes)) - Base64.strict_encode64(signing_key.sign(attrs[:key])) - end -end diff --git a/spec/fabricators/system_key_fabricator.rb b/spec/fabricators/system_key_fabricator.rb deleted file mode 100644 index bcb3bd5577..0000000000 --- a/spec/fabricators/system_key_fabricator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:system_key) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index fbaf672b5a..83ea6566e4 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -982,64 +982,6 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'with an encrypted message' do - subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) } - - let(:recipient) { Fabricate(:account) } - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'EncryptedMessage', - attributedTo: { - type: 'Device', - deviceId: '1234', - }, - to: { - type: 'Device', - deviceId: target_device.device_id, - }, - messageType: 1, - cipherText: 'Foo', - messageFranking: 'Baz678', - digest: { - digestAlgorithm: 'Bar456', - digestValue: 'Foo123', - }, - } - end - let(:target_device) { Fabricate(:device, account: recipient) } - - before do - subject.perform - end - - it 'creates an encrypted message' do - encrypted_message = target_device.encrypted_messages.reload.first - - expect(encrypted_message) - .to be_present - .and have_attributes( - from_device_id: eq('1234'), - from_account: eq(sender), - type: eq(1), - body: eq('Foo'), - digest: eq('Foo123') - ) - end - - it 'creates a message franking' do - encrypted_message = target_device.encrypted_messages.reload.first - message_franking = encrypted_message.message_franking - - crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj) - json = crypt.decrypt_and_verify(message_franking) - - expect(json['source_account_id']).to eq sender.id - expect(json['target_account_id']).to eq recipient.id - expect(json['original_franking']).to eq 'Baz678' - end - end - context 'when sender is followed by local users' do subject { described_class.new(json, sender, delivery: true) } diff --git a/spec/lib/vacuum/system_keys_vacuum_spec.rb b/spec/lib/vacuum/system_keys_vacuum_spec.rb deleted file mode 100644 index 84cae30411..0000000000 --- a/spec/lib/vacuum/system_keys_vacuum_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Vacuum::SystemKeysVacuum do - subject { described_class.new } - - describe '#perform' do - let!(:expired_system_key) { Fabricate(:system_key, created_at: (SystemKey::ROTATION_PERIOD * 4).ago) } - let!(:current_system_key) { Fabricate(:system_key) } - - before do - subject.perform - end - - it 'deletes the expired key' do - expect { expired_system_key.reload }.to raise_error ActiveRecord::RecordNotFound - end - - it 'does not delete the current key' do - expect { current_system_key.reload }.to_not raise_error - end - end -end diff --git a/spec/models/account_deletion_request_spec.rb b/spec/models/account_deletion_request_spec.rb new file mode 100644 index 0000000000..7dbfbed12a --- /dev/null +++ b/spec/models/account_deletion_request_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccountDeletionRequest do + describe 'Associations' do + it { is_expected.to belong_to(:account).required } + end + + describe '#due_at' do + before { stub_const 'AccountDeletionRequest::DELAY_TO_DELETION', 1.day } + + it 'returns time from created at with delay added' do + account_deletion_request = Fabricate :account_deletion_request, created_at: Date.current.at_midnight + expect(account_deletion_request.due_at) + .to be_within(0.1).of(Date.tomorrow.at_midnight) + end + end +end diff --git a/spec/models/one_time_key_spec.rb b/spec/models/one_time_key_spec.rb deleted file mode 100644 index 17fcdf3788..0000000000 --- a/spec/models/one_time_key_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe OneTimeKey do - describe 'validations' do - context 'with an invalid signature' do - let(:one_time_key) { Fabricate.build(:one_time_key, signature: 'wrong!') } - - it 'is invalid' do - expect(one_time_key).to_not be_valid - end - end - - context 'with an invalid key' do - let(:one_time_key) { Fabricate.build(:one_time_key, key: 'wrong!') } - - it 'is invalid' do - expect(one_time_key).to_not be_valid - end - end - end -end diff --git a/spec/models/preview_card_provider_spec.rb b/spec/models/preview_card_provider_spec.rb index 12bca83440..a3bd4f49ad 100644 --- a/spec/models/preview_card_provider_spec.rb +++ b/spec/models/preview_card_provider_spec.rb @@ -24,21 +24,5 @@ RSpec.describe PreviewCardProvider do expect(results).to eq([not_trendable_and_not_reviewed]) end end - - describe 'reviewed' do - it 'returns the relevant records' do - results = described_class.reviewed - - expect(results).to eq([trendable_and_reviewed]) - end - end - - describe 'pending_review' do - it 'returns the relevant records' do - results = described_class.pending_review - - expect(results).to eq([not_trendable_and_not_reviewed]) - end - end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fcff4c0d3b..972453cd69 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4,6 +4,8 @@ require 'rails_helper' require 'devise_two_factor/spec_helpers' RSpec.describe User do + subject { described_class.new(account: account) } + let(:password) { 'abcd1234' } let(:account) { Fabricate(:account, username: 'alice') } diff --git a/spec/requests/api/oembed_spec.rb b/spec/requests/api/oembed_spec.rb index b9578b37c8..767f20dddf 100644 --- a/spec/requests/api/oembed_spec.rb +++ b/spec/requests/api/oembed_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'API OEmbed' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['Cache-Control']) .to include('private, no-store') end @@ -27,6 +29,8 @@ RSpec.describe 'API OEmbed' do expect(response) .to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb index 77b815945e..966d1f598e 100644 --- a/spec/requests/api/v1/accounts/credentials_spec.rb +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -20,6 +20,8 @@ RSpec.describe 'credentials API' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ source: hash_including({ discoverable: false, @@ -36,6 +38,8 @@ RSpec.describe 'credentials API' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ locked: true, @@ -75,6 +79,8 @@ RSpec.describe 'credentials API' do it 'returns http success' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -84,6 +90,8 @@ RSpec.describe 'credentials API' do it 'returns http unprocessable entity' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -92,6 +100,8 @@ RSpec.describe 'credentials API' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include({ source: hash_including({ diff --git a/spec/requests/api/v1/accounts/familiar_followers_spec.rb b/spec/requests/api/v1/accounts/familiar_followers_spec.rb index 8edfa4c883..c698c2d689 100644 --- a/spec/requests/api/v1/accounts/familiar_followers_spec.rb +++ b/spec/requests/api/v1/accounts/familiar_followers_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Accounts Familiar Followers API' do get '/api/v1/accounts/familiar_followers', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are duplicate account IDs in the params' do diff --git a/spec/requests/api/v1/accounts/featured_tags_spec.rb b/spec/requests/api/v1/accounts/featured_tags_spec.rb index f48ed01def..632db65c44 100644 --- a/spec/requests/api/v1/accounts/featured_tags_spec.rb +++ b/spec/requests/api/v1/accounts/featured_tags_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'account featured tags API' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly(a_hash_including({ name: 'bar', url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/bar", @@ -37,6 +39,8 @@ RSpec.describe 'account featured tags API' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly(a_hash_including({ name: 'bar', url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/bar", diff --git a/spec/requests/api/v1/accounts/follower_accounts_spec.rb b/spec/requests/api/v1/accounts/follower_accounts_spec.rb index 2672615390..7db9884a57 100644 --- a/spec/requests/api/v1/accounts/follower_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/follower_accounts_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'API V1 Accounts FollowerAccounts' do get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 2 expect([response.parsed_body[0][:id], response.parsed_body[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end @@ -30,6 +32,8 @@ RSpec.describe 'API V1 Accounts FollowerAccounts' do get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 1 expect(response.parsed_body[0][:id]).to eq alice.id.to_s end diff --git a/spec/requests/api/v1/accounts/following_accounts_spec.rb b/spec/requests/api/v1/accounts/following_accounts_spec.rb index 19105ebf2f..ffb7332c4e 100644 --- a/spec/requests/api/v1/accounts/following_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/following_accounts_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'API V1 Accounts FollowingAccounts' do get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 2 expect([response.parsed_body[0][:id], response.parsed_body[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end @@ -30,6 +32,8 @@ RSpec.describe 'API V1 Accounts FollowingAccounts' do get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 1 expect(response.parsed_body[0][:id]).to eq alice.id.to_s end diff --git a/spec/requests/api/v1/accounts/identity_proofs_spec.rb b/spec/requests/api/v1/accounts/identity_proofs_spec.rb index d1d9db8e73..ba04ed45b9 100644 --- a/spec/requests/api/v1/accounts/identity_proofs_spec.rb +++ b/spec/requests/api/v1/accounts/identity_proofs_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Accounts Identity Proofs API' do get "/api/v1/accounts/#{account.id}/identity_proofs", params: { limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/lists_spec.rb b/spec/requests/api/v1/accounts/lists_spec.rb index 8b04f07f65..cb1ff6b9f2 100644 --- a/spec/requests/api/v1/accounts/lists_spec.rb +++ b/spec/requests/api/v1/accounts/lists_spec.rb @@ -20,6 +20,8 @@ RSpec.describe 'Accounts Lists API' do get "/api/v1/accounts/#{account.id}/lists", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/lookup_spec.rb b/spec/requests/api/v1/accounts/lookup_spec.rb index dfd9fad49d..77c09c0902 100644 --- a/spec/requests/api/v1/accounts/lookup_spec.rb +++ b/spec/requests/api/v1/accounts/lookup_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Accounts Lookup API' do get '/api/v1/accounts/lookup', params: { account_id: account.id, acct: account.acct }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/notes_spec.rb b/spec/requests/api/v1/accounts/notes_spec.rb index b8c493abcc..1677ec07e3 100644 --- a/spec/requests/api/v1/accounts/notes_spec.rb +++ b/spec/requests/api/v1/accounts/notes_spec.rb @@ -22,6 +22,8 @@ RSpec.describe 'Accounts Notes API' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(AccountNote.find_by(account_id: user.account.id, target_account_id: account.id).comment).to eq comment end end @@ -33,6 +35,8 @@ RSpec.describe 'Accounts Notes API' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(AccountNote.where(account_id: user.account.id, target_account_id: account.id)).to_not exist end end diff --git a/spec/requests/api/v1/accounts/pins_spec.rb b/spec/requests/api/v1/accounts/pins_spec.rb index c66b80c7fd..8ebcb27d28 100644 --- a/spec/requests/api/v1/accounts/pins_spec.rb +++ b/spec/requests/api/v1/accounts/pins_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'Accounts Pins API' do subject end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ RSpec.describe 'Accounts Pins API' do subject end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/relationships_spec.rb b/spec/requests/api/v1/accounts/relationships_spec.rb index 9570d1214c..52aeb01328 100644 --- a/spec/requests/api/v1/accounts/relationships_spec.rb +++ b/spec/requests/api/v1/accounts/relationships_spec.rb @@ -29,6 +29,8 @@ RSpec.describe 'GET /api/v1/accounts/relationships' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and contain_exactly( @@ -50,6 +52,8 @@ RSpec.describe 'GET /api/v1/accounts/relationships' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and have_attributes( @@ -70,6 +74,8 @@ RSpec.describe 'GET /api/v1/accounts/relationships' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) .and have_attributes( @@ -149,6 +155,8 @@ RSpec.describe 'GET /api/v1/accounts/relationships' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) @@ -171,6 +179,8 @@ RSpec.describe 'GET /api/v1/accounts/relationships' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Enumerable) diff --git a/spec/requests/api/v1/accounts/search_spec.rb b/spec/requests/api/v1/accounts/search_spec.rb index f6ab7a8531..dc24813e73 100644 --- a/spec/requests/api/v1/accounts/search_spec.rb +++ b/spec/requests/api/v1/accounts/search_spec.rb @@ -13,6 +13,8 @@ RSpec.describe 'Accounts Search API' do get '/api/v1/accounts/search', params: { q: 'query' }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts/statuses_spec.rb b/spec/requests/api/v1/accounts/statuses_spec.rb index e056a78901..1e21950287 100644 --- a/spec/requests/api/v1/accounts/statuses_spec.rb +++ b/spec/requests/api/v1/accounts/statuses_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'API V1 Accounts Statuses' do prev: api_v1_account_statuses_url(limit: 1, min_id: status.id), next: api_v1_account_statuses_url(limit: 1, max_id: status.id) ) + expect(response.content_type) + .to start_with('application/json') end context 'with only media' do @@ -26,6 +28,8 @@ RSpec.describe 'API V1 Accounts Statuses' do get "/api/v1/accounts/#{user.account.id}/statuses", params: { only_media: true }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -41,6 +45,8 @@ RSpec.describe 'API V1 Accounts Statuses' do it 'returns posts along with self replies', :aggregate_failures do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to have_attributes(size: 2) .and contain_exactly( @@ -61,6 +67,8 @@ RSpec.describe 'API V1 Accounts Statuses' do expect(response) .to have_http_status(200) .and include_pagination_headers(prev: api_v1_account_statuses_url(pinned: true, min_id: Status.first.id)) + expect(response.content_type) + .to start_with('application/json') end end @@ -79,6 +87,8 @@ RSpec.describe 'API V1 Accounts Statuses' do prev: api_v1_account_statuses_url(pinned: true, min_id: Status.first.id), next: api_v1_account_statuses_url(pinned: true, max_id: Status.first.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -96,6 +106,8 @@ RSpec.describe 'API V1 Accounts Statuses' do get "/api/v1/accounts/#{account.id}/statuses", params: { pinned: true }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when user does not follow account' do @@ -122,6 +134,8 @@ RSpec.describe 'API V1 Accounts Statuses' do a_hash_including(id: status.id.to_s), a_hash_including(id: private_status.id.to_s) ) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb index 2ebe56fa7d..45e66f0744 100644 --- a/spec/requests/api/v1/accounts_spec.rb +++ b/spec/requests/api/v1/accounts_spec.rb @@ -17,6 +17,8 @@ RSpec.describe '/api/v1/accounts' do get '/api/v1/accounts', headers: headers, params: { id: [account.id, other_account.id, 123_123] } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly( hash_including(id: account.id.to_s), hash_including(id: other_account.id.to_s) @@ -32,6 +34,8 @@ RSpec.describe '/api/v1/accounts' do get "/api/v1/accounts/#{account.id}" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id]).to eq(account.id.to_s) end end @@ -41,6 +45,8 @@ RSpec.describe '/api/v1/accounts' do get '/api/v1/accounts/1' expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:error]).to eq('Record not found') end end @@ -57,6 +63,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id]).to eq(account.id.to_s) end @@ -80,6 +88,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:access_token]).to_not be_blank user = User.find_by(email: 'hello@world.tld') @@ -93,6 +103,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -113,6 +125,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -133,6 +147,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -203,6 +219,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be false end @@ -225,6 +243,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.followed_by?(other_account)).to be false end @@ -247,6 +267,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be false expect(user.account.blocking?(other_account)).to be true end @@ -270,6 +292,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.blocking?(other_account)).to be false end @@ -292,6 +316,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be true @@ -316,6 +342,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be false @@ -340,6 +368,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.following?(other_account)).to be true expect(user.account.muting?(other_account)).to be true expect(user.account.muting_notifications?(other_account)).to be true @@ -364,6 +394,8 @@ RSpec.describe '/api/v1/accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.muting?(other_account)).to be false end diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb index 5bcf809401..4884dba9c7 100644 --- a/spec/requests/api/v1/admin/account_actions_spec.rb +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -61,6 +61,8 @@ RSpec.describe 'Account actions' do it 'disables the target account' do expect { subject }.to change { target_account.reload.user_disabled? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -75,6 +77,8 @@ RSpec.describe 'Account actions' do it 'marks the target account as sensitive' do expect { subject }.to change { target_account.reload.sensitized? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -89,6 +93,8 @@ RSpec.describe 'Account actions' do it 'marks the target account as silenced' do expect { subject }.to change { target_account.reload.silenced? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -103,6 +109,8 @@ RSpec.describe 'Account actions' do it 'marks the target account as suspended' do expect { subject }.to change { target_account.reload.suspended? }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -115,6 +123,8 @@ RSpec.describe 'Account actions' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -125,6 +135,8 @@ RSpec.describe 'Account actions' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -135,6 +147,8 @@ RSpec.describe 'Account actions' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb index 2dc45d5eb2..6a681f9c5e 100644 --- a/spec/requests/api/v1/admin/accounts_spec.rb +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_results.map { |a| a.id.to_s }) end end @@ -93,6 +95,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) end end @@ -112,6 +116,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including(id: account.id.to_s, username: account.username, email: account.user.email) ) @@ -122,6 +128,8 @@ RSpec.describe 'Accounts' do get '/api/v1/admin/accounts/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -145,6 +153,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.user_approved?).to be(true) end @@ -166,6 +176,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -174,6 +186,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/approve', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -193,15 +207,13 @@ RSpec.describe 'Accounts' do it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' it_behaves_like 'forbidden for wrong role', '' - it 'removes the user successfully', :aggregate_failures do + it 'removes the user successfully and logs action', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(User.where(id: account.user.id)).to_not exist - end - - it 'logs action', :aggregate_failures do - subject expect(latest_admin_action_log) .to be_present @@ -218,6 +230,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -226,6 +240,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/reject', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -248,6 +264,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.user_disabled?).to be false end @@ -256,6 +274,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/enable', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -279,6 +299,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.suspended?).to be false end end @@ -288,6 +310,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -296,6 +320,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/unsuspend', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -318,6 +344,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.sensitized?).to be false end @@ -326,6 +354,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/unsensitive', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -348,6 +378,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(account.reload.silenced?).to be false end @@ -356,6 +388,8 @@ RSpec.describe 'Accounts' do post '/api/v1/admin/accounts/-1/unsilence', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -380,6 +414,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Admin::AccountDeletionWorker).to have_received(:perform_async).with(account.id).once end end @@ -397,6 +433,8 @@ RSpec.describe 'Accounts' do delete '/api/v1/admin/accounts/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb index dd7e119911..eaa011d516 100644 --- a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -24,6 +24,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no canonical email block' do @@ -96,6 +98,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( id: eq(canonical_email_block.id.to_s), @@ -109,6 +113,8 @@ RSpec.describe 'Canonical Email Blocks' do get '/api/v1/admin/canonical_email_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -131,6 +137,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -142,6 +150,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.first[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end @@ -151,6 +161,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty end end @@ -173,6 +185,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end @@ -183,6 +197,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -193,6 +209,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(params[:canonical_email_hash]) end end @@ -204,6 +222,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end @@ -217,6 +237,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -237,6 +259,8 @@ RSpec.describe 'Canonical Email Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil end @@ -245,6 +269,8 @@ RSpec.describe 'Canonical Email Blocks' do delete '/api/v1/admin/canonical_email_blocks/0', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/dimensions_spec.rb b/spec/requests/api/v1/admin/dimensions_spec.rb index a28c2a9e3e..81fb580ba7 100644 --- a/spec/requests/api/v1/admin/dimensions_spec.rb +++ b/spec/requests/api/v1/admin/dimensions_spec.rb @@ -15,6 +15,8 @@ RSpec.describe 'Admin Dimensions' do expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -27,6 +29,9 @@ RSpec.describe 'Admin Dimensions' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_an(Array) end diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb index 26c962b347..eb5128e420 100644 --- a/spec/requests/api/v1/admin/domain_allows_spec.rb +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -24,6 +24,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no allowed domains' do @@ -79,6 +81,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq domain_allow.domain end @@ -87,6 +91,8 @@ RSpec.describe 'Domain Allows' do get '/api/v1/admin/domain_allows/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -107,6 +113,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq 'foo.bar.com' expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present end @@ -119,6 +127,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -129,6 +139,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -160,6 +172,8 @@ RSpec.describe 'Domain Allows' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil end @@ -168,6 +182,8 @@ RSpec.describe 'Domain Allows' do delete '/api/v1/admin/domain_allows/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index 3f2cbbf113..1a506bd9be 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -24,6 +24,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no domain blocks' do @@ -94,6 +96,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( id: domain_block.id.to_s, domain: domain_block.domain, @@ -113,6 +117,8 @@ RSpec.describe 'Domain Blocks' do get '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -132,6 +138,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { domain: 'foo.bar.com', @@ -153,6 +161,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { domain: 'foo.bar.com', @@ -173,6 +183,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:existing_domain_block][:domain]).to eq('foo.bar.com') end end @@ -186,6 +198,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:existing_domain_block][:domain]).to eq('bar.com') end end @@ -197,6 +211,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -217,6 +233,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match a_hash_including( { id: domain_block.id.to_s, @@ -236,6 +254,8 @@ RSpec.describe 'Domain Blocks' do put '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -255,6 +275,8 @@ RSpec.describe 'Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(DomainBlock.find_by(id: domain_block.id)).to be_nil end @@ -263,6 +285,8 @@ RSpec.describe 'Domain Blocks' do delete '/api/v1/admin/domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb index aa3073341a..3f51a3ea2d 100644 --- a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -25,6 +25,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no email domain block' do @@ -97,6 +99,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq(email_domain_block.domain) end end @@ -106,6 +110,8 @@ RSpec.describe 'Email Domain Blocks' do get '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -125,6 +131,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:domain]).to eq(params[:domain]) end @@ -135,6 +143,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -145,6 +155,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -157,6 +169,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -176,6 +190,8 @@ RSpec.describe 'Email Domain Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil end @@ -185,6 +201,8 @@ RSpec.describe 'Email Domain Blocks' do delete '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index b18f8f885c..c096aa3328 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -24,6 +24,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there is no ip block' do @@ -88,6 +90,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -101,6 +105,8 @@ RSpec.describe 'IP Blocks' do get '/api/v1/admin/ip_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -120,6 +126,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( ip: eq("#{params[:ip]}/32"), @@ -135,6 +143,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -145,6 +155,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -157,6 +169,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -167,6 +181,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -185,6 +201,8 @@ RSpec.describe 'IP Blocks' do .and change_comment_value expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match(hash_including({ ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", severity: 'sign_up_requires_approval', @@ -205,6 +223,8 @@ RSpec.describe 'IP Blocks' do put '/api/v1/admin/ip_blocks/-1', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -220,6 +240,8 @@ RSpec.describe 'IP Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty expect(IpBlock.find_by(id: ip_block.id)).to be_nil end @@ -229,6 +251,8 @@ RSpec.describe 'IP Blocks' do delete '/api/v1/admin/ip_blocks/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/measures_spec.rb b/spec/requests/api/v1/admin/measures_spec.rb index de359a5ccd..519b5cc7b3 100644 --- a/spec/requests/api/v1/admin/measures_spec.rb +++ b/spec/requests/api/v1/admin/measures_spec.rb @@ -32,6 +32,8 @@ RSpec.describe 'Admin Measures' do expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -43,6 +45,8 @@ RSpec.describe 'Admin Measures' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 2c40f56dc8..a0e7b0a369 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'Reports' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no reports' do @@ -126,6 +128,8 @@ RSpec.describe 'Reports' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( { id: report.id.to_s, @@ -156,6 +160,8 @@ RSpec.describe 'Reports' do .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') report.reload @@ -190,6 +196,8 @@ RSpec.describe 'Reports' do .to change { report.reload.unresolved? }.from(true).to(false) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -208,6 +216,8 @@ RSpec.describe 'Reports' do .to change { report.reload.unresolved? }.from(false).to(true) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -226,6 +236,8 @@ RSpec.describe 'Reports' do .to change { report.reload.assigned_account_id }.from(nil).to(user.account.id) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -244,6 +256,8 @@ RSpec.describe 'Reports' do .to change { report.reload.assigned_account_id }.from(user.account.id).to(nil) .and create_an_action_log expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/admin/retention_spec.rb b/spec/requests/api/v1/admin/retention_spec.rb index c28fa6de81..e28bc2a94f 100644 --- a/spec/requests/api/v1/admin/retention_spec.rb +++ b/spec/requests/api/v1/admin/retention_spec.rb @@ -15,6 +15,8 @@ RSpec.describe 'Admin Retention' do expect(response) .to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ RSpec.describe 'Admin Retention' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb index 2f730cdeb8..3623c09ac7 100644 --- a/spec/requests/api/v1/admin/tags_spec.rb +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -24,6 +24,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when there are no tags' do @@ -73,14 +75,12 @@ RSpec.describe 'Tags' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'returns http success and expected tag content' do subject expect(response).to have_http_status(200) - end - - it 'returns expected tag content' do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name) @@ -91,6 +91,8 @@ RSpec.describe 'Tags' do get '/api/v1/admin/tags/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -107,14 +109,12 @@ RSpec.describe 'Tags' do it_behaves_like 'forbidden for wrong scope', 'admin:read' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'returns http success and updates tag' do subject expect(response).to have_http_status(200) - end - - it 'returns updated tag' do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:id].to_i).to eq(tag.id) expect(response.parsed_body[:name]).to eq(tag.name.upcase) @@ -127,6 +127,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -135,6 +137,8 @@ RSpec.describe 'Tags' do get '/api/v1/admin/tags/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/links/links_spec.rb b/spec/requests/api/v1/admin/trends/links/links_spec.rb index c436b7081e..51e800734a 100644 --- a/spec/requests/api/v1/admin/trends/links/links_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/links_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'Links' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ RSpec.describe 'Links' do .to change_link_trendable_to_true expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expects_correct_link_data end @@ -60,6 +64,8 @@ RSpec.describe 'Links' do post '/api/v1/admin/trends/links/-1/approve', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -70,6 +76,8 @@ RSpec.describe 'Links' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end end @@ -89,6 +97,8 @@ RSpec.describe 'Links' do .to_not change_link_trendable expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end def change_link_trendable @@ -114,6 +124,8 @@ RSpec.describe 'Links' do post '/api/v1/admin/trends/links/-1/reject', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -124,6 +136,8 @@ RSpec.describe 'Links' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb index 193906ab05..d46d0ff555 100644 --- a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb @@ -16,6 +16,8 @@ RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do get '/api/v1/admin/trends/links/publishers', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/statuses_spec.rb b/spec/requests/api/v1/admin/trends/statuses_spec.rb index e33a9658a9..c63d8d925c 100644 --- a/spec/requests/api/v1/admin/trends/statuses_spec.rb +++ b/spec/requests/api/v1/admin/trends/statuses_spec.rb @@ -16,6 +16,8 @@ RSpec.describe 'API V1 Admin Trends Statuses' do get '/api/v1/admin/trends/statuses', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ RSpec.describe 'API V1 Admin Trends Statuses' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ RSpec.describe 'API V1 Admin Trends Statuses' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/admin/trends/tags_spec.rb b/spec/requests/api/v1/admin/trends/tags_spec.rb index 748a27283c..433cc6c5a6 100644 --- a/spec/requests/api/v1/admin/trends/tags_spec.rb +++ b/spec/requests/api/v1/admin/trends/tags_spec.rb @@ -16,6 +16,8 @@ RSpec.describe 'API V1 Admin Trends Tags' do get '/api/v1/admin/trends/tags', params: { account_id: account.id, limit: 2 }, headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ RSpec.describe 'API V1 Admin Trends Tags' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ RSpec.describe 'API V1 Admin Trends Tags' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/announcements/reactions_spec.rb b/spec/requests/api/v1/announcements/reactions_spec.rb index ffacb2b0af..82154b4a26 100644 --- a/spec/requests/api/v1/announcements/reactions_spec.rb +++ b/spec/requests/api/v1/announcements/reactions_spec.rb @@ -15,7 +15,9 @@ RSpec.describe 'API V1 Announcements Reactions' do it 'returns http unauthorized' do put "/api/v1/announcements/#{announcement.id}/reactions/#{escaped_emoji}" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ RSpec.describe 'API V1 Announcements Reactions' do it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to_not be_nil end end @@ -39,7 +43,9 @@ RSpec.describe 'API V1 Announcements Reactions' do context 'without token' do it 'returns http unauthorized' do delete "/api/v1/announcements/#{announcement.id}/reactions/#{escaped_emoji}" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -50,6 +56,8 @@ RSpec.describe 'API V1 Announcements Reactions' do it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to be_nil end end diff --git a/spec/requests/api/v1/announcements_spec.rb b/spec/requests/api/v1/announcements_spec.rb index 1624b76012..97a4442aa9 100644 --- a/spec/requests/api/v1/announcements_spec.rb +++ b/spec/requests/api/v1/announcements_spec.rb @@ -15,7 +15,9 @@ RSpec.describe 'API V1 Announcements' do it 'returns http unprocessable entity' do get '/api/v1/announcements' - expect(response).to have_http_status 422 + expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -26,6 +28,8 @@ RSpec.describe 'API V1 Announcements' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -35,7 +39,9 @@ RSpec.describe 'API V1 Announcements' do it 'returns http unauthorized' do post "/api/v1/announcements/#{announcement.id}/dismiss" - expect(response).to have_http_status 401 + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -48,6 +54,8 @@ RSpec.describe 'API V1 Announcements' do it 'dismisses announcement', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(announcement.announcement_mutes.find_by(account: user.account)).to_not be_nil end end diff --git a/spec/requests/api/v1/annual_reports_spec.rb b/spec/requests/api/v1/annual_reports_spec.rb index 8051a65484..b9831d17e2 100644 --- a/spec/requests/api/v1/annual_reports_spec.rb +++ b/spec/requests/api/v1/annual_reports_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'API V1 Annual Reports' do expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -33,6 +35,8 @@ RSpec.describe 'API V1 Annual Reports' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -51,6 +55,8 @@ RSpec.describe 'API V1 Annual Reports' do .to change { annual_report.reload.viewed? }.to(true) expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 1cd6a4178f..30200ed60c 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -17,6 +17,8 @@ RSpec.describe 'Credentials' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( @@ -36,6 +38,8 @@ RSpec.describe 'Credentials' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:client_id]).to_not be_present expect(response.parsed_body[:client_secret]).to_not be_present @@ -47,14 +51,12 @@ RSpec.describe 'Credentials' do let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } - it 'returns http success' do + it 'returns http success and returns app information' do subject expect(response).to have_http_status(200) - end - - it 'returns the app information correctly' do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( @@ -78,6 +80,8 @@ RSpec.describe 'Credentials' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -90,6 +94,8 @@ RSpec.describe 'Credentials' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end it 'returns the error in the json response' do @@ -108,14 +114,12 @@ RSpec.describe 'Credentials' do let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } } - it 'returns http authorization error' do + it 'returns http authorization error with json error' do subject expect(response).to have_http_status(401) - end - - it 'returns the error in the json response' do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( diff --git a/spec/requests/api/v1/apps_spec.rb b/spec/requests/api/v1/apps_spec.rb index 51a0c3fd0c..cf43e14d62 100644 --- a/spec/requests/api/v1/apps_spec.rb +++ b/spec/requests/api/v1/apps_spec.rb @@ -28,6 +28,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -59,6 +61,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Doorkeeper::Application.find_by(name: client_name)).to be_present expect(response.parsed_body) @@ -76,6 +80,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -96,6 +102,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -106,6 +114,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read' end end @@ -117,6 +127,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,6 +139,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -137,6 +151,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -148,6 +164,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -158,6 +176,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -180,6 +200,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) @@ -202,6 +224,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -212,6 +236,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -222,6 +248,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -232,6 +260,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -242,6 +272,8 @@ RSpec.describe 'Apps' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') app = Doorkeeper::Application.find_by(name: client_name) diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb index d2f1c46a5b..498cf93275 100644 --- a/spec/requests/api/v1/blocks_spec.rb +++ b/spec/requests/api/v1/blocks_spec.rb @@ -26,21 +26,20 @@ RSpec.describe 'Blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end context 'with limit param' do let(:params) { { limit: 2 } } - it 'returns only the requested number of blocked accounts' do + it 'returns only the requested number of blocked accounts and sets link header pagination' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets correct link header pagination' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id), diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb index 95a71abcac..c78e691236 100644 --- a/spec/requests/api/v1/bookmarks_spec.rb +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -24,15 +24,12 @@ RSpec.describe 'Bookmarks' do it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success and the bookmarked statuses' do subject expect(response).to have_http_status(200) - end - - it 'returns the bookmarked statuses' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -45,6 +42,8 @@ RSpec.describe 'Bookmarks' do expect(response.parsed_body.size) .to eq(params[:limit]) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id), @@ -60,6 +59,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/conversations_spec.rb b/spec/requests/api/v1/conversations_spec.rb index bd3cbfd0e7..6e2ac1df53 100644 --- a/spec/requests/api/v1/conversations_spec.rb +++ b/spec/requests/api/v1/conversations_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'API V1 Conversations' do prev: api_v1_conversations_url(limit: 1, min_id: Status.first.id), next: api_v1_conversations_url(limit: 1, max_id: Status.first.id) ) + expect(response.content_type) + .to start_with('application/json') end it 'returns conversations', :aggregate_failures do diff --git a/spec/requests/api/v1/custom_emojis_spec.rb b/spec/requests/api/v1/custom_emojis_spec.rb index 0942734ff3..e860fbeb17 100644 --- a/spec/requests/api/v1/custom_emojis_spec.rb +++ b/spec/requests/api/v1/custom_emojis_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'Custom Emojis' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -33,6 +35,8 @@ RSpec.describe 'Custom Emojis' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/directories_spec.rb b/spec/requests/api/v1/directories_spec.rb index aa602a71cd..282be9a582 100644 --- a/spec/requests/api/v1/directories_spec.rb +++ b/spec/requests/api/v1/directories_spec.rb @@ -82,6 +82,8 @@ RSpec.describe 'Directories API' do get '/api/v1/directory', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.pluck(:id)).to contain_exactly(eligible_remote_account.id.to_s, local_discoverable_account.id.to_s) end @@ -101,6 +103,8 @@ RSpec.describe 'Directories API' do get '/api/v1/directory', headers: headers, params: { local: '1' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(1) expect(response.parsed_body.first[:id]).to include(local_account.id.to_s) expect(response.body).to_not include(remote_account.id.to_s) @@ -115,6 +119,8 @@ RSpec.describe 'Directories API' do get '/api/v1/directory', headers: headers, params: { order: 'active' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.first[:id]).to include(new_stat.account_id.to_s) expect(response.parsed_body.second[:id]).to include(old_stat.account_id.to_s) @@ -130,6 +136,8 @@ RSpec.describe 'Directories API' do get '/api/v1/directory', headers: headers, params: { order: 'new' } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(2) expect(response.parsed_body.first[:id]).to include(account_new.id.to_s) expect(response.parsed_body.second[:id]).to include(account_old.id.to_s) diff --git a/spec/requests/api/v1/domain_blocks_spec.rb b/spec/requests/api/v1/domain_blocks_spec.rb index 8184c26bed..339f49fe76 100644 --- a/spec/requests/api/v1/domain_blocks_spec.rb +++ b/spec/requests/api/v1/domain_blocks_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(blocked_domains) end @@ -53,6 +55,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.domain_blocking?(params[:domain])).to be(true) end @@ -63,6 +67,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -73,6 +79,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -94,6 +102,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.domain_blocking?('example.com')).to be(false) end @@ -104,6 +114,8 @@ RSpec.describe 'Domain blocks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/emails/confirmations_spec.rb b/spec/requests/api/v1/emails/confirmations_spec.rb index 0a419a10cf..1408ad0506 100644 --- a/spec/requests/api/v1/emails/confirmations_spec.rb +++ b/spec/requests/api/v1/emails/confirmations_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end end @@ -41,6 +43,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') end context 'when user changed e-mail and has not confirmed it' do @@ -52,6 +56,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -61,6 +67,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -71,6 +79,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.reload.unconfirmed_email).to eq('foo@bar.com') end end @@ -82,6 +92,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -94,6 +106,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -111,6 +125,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be false end end @@ -122,6 +138,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be true end end @@ -139,6 +157,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be false end end @@ -150,6 +170,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be true end end @@ -162,6 +184,8 @@ RSpec.describe 'Confirmations' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/endorsements_spec.rb b/spec/requests/api/v1/endorsements_spec.rb index 25917f527a..730ba6350c 100644 --- a/spec/requests/api/v1/endorsements_spec.rb +++ b/spec/requests/api/v1/endorsements_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Endorsements' do expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -36,6 +38,8 @@ RSpec.describe 'Endorsements' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -51,6 +55,8 @@ RSpec.describe 'Endorsements' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to_not be_present diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb index 78e9d61551..44d0239556 100644 --- a/spec/requests/api/v1/favourites_spec.rb +++ b/spec/requests/api/v1/favourites_spec.rb @@ -24,35 +24,29 @@ RSpec.describe 'Favourites' do it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success and includes the favourites' do subject expect(response).to have_http_status(200) - end - - it 'returns the favourites' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of favourites' do + it 'returns only the requested number of favourites and sets pagination headers' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers' do - subject - expect(response) .to include_pagination_headers( prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id), next: api_v1_favourites_url(limit: params[:limit], max_id: favourites.second.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -63,6 +57,8 @@ RSpec.describe 'Favourites' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/featured_tags/suggestions_spec.rb b/spec/requests/api/v1/featured_tags/suggestions_spec.rb index 8815c65cf1..5fbbec5097 100644 --- a/spec/requests/api/v1/featured_tags/suggestions_spec.rb +++ b/spec/requests/api/v1/featured_tags/suggestions_spec.rb @@ -32,6 +32,8 @@ RSpec.describe 'Featured Tags Suggestions API' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(name: used_tag.name) diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb index 423cc0c560..b9c78cc11b 100644 --- a/spec/requests/api/v1/featured_tags_spec.rb +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -22,6 +22,8 @@ RSpec.describe 'FeaturedTags' do get '/api/v1/featured_tags' expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ RSpec.describe 'FeaturedTags' do get '/api/v1/featured_tags', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when the requesting user has no featured tag' do @@ -58,15 +62,12 @@ RSpec.describe 'FeaturedTags' do describe 'POST /api/v1/featured_tags' do let(:params) { { name: 'tag' } } - it 'returns http success' do + it 'returns http success and includes correct tag name' do post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(200) - end - - it 'returns the correct tag name' do - post '/api/v1/featured_tags', headers: headers, params: params - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( name: params[:name] @@ -94,6 +95,8 @@ RSpec.describe 'FeaturedTags' do post '/api/v1/featured_tags', params: params expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -102,6 +105,8 @@ RSpec.describe 'FeaturedTags' do post '/api/v1/featured_tags', headers: headers expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -112,6 +117,8 @@ RSpec.describe 'FeaturedTags' do post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -124,6 +131,8 @@ RSpec.describe 'FeaturedTags' do post '/api/v1/featured_tags', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -132,23 +141,15 @@ RSpec.describe 'FeaturedTags' do let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) } let(:id) { featured_tag.id } - it 'returns http success' do + it 'returns http success with an empty body and deletes the featured tag', :inline_jobs do delete "/api/v1/featured_tags/#{id}", headers: headers expect(response).to have_http_status(200) - end - - it 'returns an empty body' do - delete "/api/v1/featured_tags/#{id}", headers: headers - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to be_empty - end - - it 'deletes the featured tag', :inline_jobs do - delete "/api/v1/featured_tags/#{id}", headers: headers featured_tag = FeaturedTag.find_by(id: id) - expect(featured_tag).to be_nil end @@ -165,6 +166,8 @@ RSpec.describe 'FeaturedTags' do delete "/api/v1/featured_tags/#{id}" expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -173,6 +176,8 @@ RSpec.describe 'FeaturedTags' do delete '/api/v1/featured_tags/0', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -184,6 +189,8 @@ RSpec.describe 'FeaturedTags' do delete "/api/v1/featured_tags/#{id}", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/filters_spec.rb b/spec/requests/api/v1/filters_spec.rb index 93ed78b346..51f03cc04d 100644 --- a/spec/requests/api/v1/filters_spec.rb +++ b/spec/requests/api/v1/filters_spec.rb @@ -15,6 +15,8 @@ RSpec.describe 'API V1 Filters' do it 'returns http success' do get '/api/v1/filters', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: custom_filter_keyword.id.to_s) @@ -35,6 +37,8 @@ RSpec.describe 'API V1 Filters' do filter = user.account.custom_filters.first expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -50,6 +54,8 @@ RSpec.describe 'API V1 Filters' do filter = user.account.custom_filters.first expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -68,6 +74,8 @@ RSpec.describe 'API V1 Filters' do get "/api/v1/filters/#{keyword.id}", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -82,6 +90,8 @@ RSpec.describe 'API V1 Filters' do it 'updates the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.phrase).to eq 'updated' end end @@ -97,6 +107,8 @@ RSpec.describe 'API V1 Filters' do it 'removes the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/requests/api/v1/follow_requests_spec.rb b/spec/requests/api/v1/follow_requests_spec.rb index c143ccaec1..f0f73d38ad 100644 --- a/spec/requests/api/v1/follow_requests_spec.rb +++ b/spec/requests/api/v1/follow_requests_spec.rb @@ -36,6 +36,8 @@ RSpec.describe 'Follow requests' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -66,6 +68,8 @@ RSpec.describe 'Follow requests' do it 'allows the requesting follower to follow', :aggregate_failures do expect { subject }.to change { follower.following?(user.account) }.from(false).to(true) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:followed_by]).to be true end end @@ -87,6 +91,8 @@ RSpec.describe 'Follow requests' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(FollowRequest.where(target_account: user.account, account: follower)).to_not exist expect(response.parsed_body[:followed_by]).to be false end diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb index f7787cb763..b0191b523f 100644 --- a/spec/requests/api/v1/followed_tags_spec.rb +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -28,29 +28,24 @@ RSpec.describe 'Followed tags' do it_behaves_like 'forbidden for wrong scope', 'write write:follows' - it 'returns http success' do + it 'returns http success and includes followed tags' do subject expect(response).to have_http_status(:success) - end - - it 'returns the followed tags correctly' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of follow tags' do + it 'returns only the requested number of follow tags and sets pagination headers' do subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers' do - subject expect(response) .to include_pagination_headers( diff --git a/spec/requests/api/v1/instance_spec.rb b/spec/requests/api/v1/instance_spec.rb index 42b6753bc3..62c90f55b2 100644 --- a/spec/requests/api/v1/instance_spec.rb +++ b/spec/requests/api/v1/instance_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Instances' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present @@ -27,6 +29,8 @@ RSpec.describe 'Instances' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/activity_spec.rb b/spec/requests/api/v1/instances/activity_spec.rb index 72e3faeb65..c2f94cc578 100644 --- a/spec/requests/api/v1/instances/activity_spec.rb +++ b/spec/requests/api/v1/instances/activity_spec.rb @@ -13,6 +13,9 @@ RSpec.describe 'Activity' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and(be_an(Array)) diff --git a/spec/requests/api/v1/instances/domain_blocks_spec.rb b/spec/requests/api/v1/instances/domain_blocks_spec.rb index 460d338607..b214fda73b 100644 --- a/spec/requests/api/v1/instances/domain_blocks_spec.rb +++ b/spec/requests/api/v1/instances/domain_blocks_spec.rb @@ -17,6 +17,9 @@ RSpec.describe 'Domain Blocks' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and(be_an(Array)) diff --git a/spec/requests/api/v1/instances/extended_descriptions_spec.rb b/spec/requests/api/v1/instances/extended_descriptions_spec.rb index bf6d58216a..62a7fff2ec 100644 --- a/spec/requests/api/v1/instances/extended_descriptions_spec.rb +++ b/spec/requests/api/v1/instances/extended_descriptions_spec.rb @@ -9,6 +9,8 @@ RSpec.describe 'Extended Descriptions' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb index 79ea62c599..3d188d23c0 100644 --- a/spec/requests/api/v1/instances/languages_spec.rb +++ b/spec/requests/api/v1/instances/languages_spec.rb @@ -8,11 +8,10 @@ RSpec.describe 'Languages' do get '/api/v1/instance/languages' end - it 'returns http success' do + it 'returns http success and includes supported languages' do expect(response).to have_http_status(200) - end - - it 'returns the supported languages' do + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s) end end diff --git a/spec/requests/api/v1/instances/peers_spec.rb b/spec/requests/api/v1/instances/peers_spec.rb index 1140612f0a..8ebfc93357 100644 --- a/spec/requests/api/v1/instances/peers_spec.rb +++ b/spec/requests/api/v1/instances/peers_spec.rb @@ -12,6 +12,8 @@ RSpec.describe 'Peers' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/instances/privacy_policies_spec.rb b/spec/requests/api/v1/instances/privacy_policies_spec.rb index 93490542cd..519c2b29d0 100644 --- a/spec/requests/api/v1/instances/privacy_policies_spec.rb +++ b/spec/requests/api/v1/instances/privacy_policies_spec.rb @@ -9,6 +9,8 @@ RSpec.describe 'Privacy Policy' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/instances/rules_spec.rb b/spec/requests/api/v1/instances/rules_spec.rb index 620c991ae2..b730048863 100644 --- a/spec/requests/api/v1/instances/rules_spec.rb +++ b/spec/requests/api/v1/instances/rules_spec.rb @@ -9,6 +9,8 @@ RSpec.describe 'Rules' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_an(Array) diff --git a/spec/requests/api/v1/instances/translation_languages_spec.rb b/spec/requests/api/v1/instances/translation_languages_spec.rb index 0de5ec3bc2..fef8700db9 100644 --- a/spec/requests/api/v1/instances/translation_languages_spec.rb +++ b/spec/requests/api/v1/instances/translation_languages_spec.rb @@ -10,6 +10,8 @@ RSpec.describe 'Translation Languages' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to eq({}) @@ -24,6 +26,8 @@ RSpec.describe 'Translation Languages' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to match({ und: %w(en de), en: ['de'] }) diff --git a/spec/requests/api/v1/lists/accounts_spec.rb b/spec/requests/api/v1/lists/accounts_spec.rb index d147b21ee7..3911d1f28b 100644 --- a/spec/requests/api/v1/lists/accounts_spec.rb +++ b/spec/requests/api/v1/lists/accounts_spec.rb @@ -34,6 +34,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end @@ -68,6 +70,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to include(bob) end end @@ -81,6 +85,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to include(bob) end end @@ -90,6 +96,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to_not include(bob) end end @@ -105,6 +113,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -118,6 +128,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -139,16 +151,13 @@ RSpec.describe 'Accounts' do list.accounts << [bob, peter] end - it 'removes the specified account from the list', :aggregate_failures do + it 'removes the specified account from the list but keeps other accounts in the list', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to_not include(bob) - end - - it 'does not remove any other account from the list' do - subject - expect(list.accounts).to include(peter) end @@ -159,6 +168,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(list.accounts).to contain_exactly(bob, peter) end end @@ -172,6 +183,8 @@ RSpec.describe 'Accounts' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index 2042a64d5c..20f27a7431 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -43,6 +43,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array(expected_response) end end @@ -60,6 +62,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: list.id.to_s, title: list.title, @@ -75,6 +79,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -83,6 +89,8 @@ RSpec.describe 'Lists' do get '/api/v1/lists/-1', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -100,6 +108,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match(a_hash_including(title: 'my list', replies_policy: 'none', exclusive: true)) expect(List.where(account: user.account).count).to eq(1) end @@ -111,6 +121,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -121,6 +133,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -142,6 +156,8 @@ RSpec.describe 'Lists' do .and change_list_exclusive expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') list.reload expect(response.parsed_body).to match({ @@ -169,6 +185,8 @@ RSpec.describe 'Lists' do put '/api/v1/lists/-1', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -179,6 +197,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -196,6 +216,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(List.where(id: list.id)).to_not exist end @@ -214,6 +236,8 @@ RSpec.describe 'Lists' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/markers_spec.rb b/spec/requests/api/v1/markers_spec.rb index a10d2dc3e2..d7cd78924b 100644 --- a/spec/requests/api/v1/markers_spec.rb +++ b/spec/requests/api/v1/markers_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'API Markers' do it 'returns markers', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( home: include(last_read_id: '123'), @@ -34,6 +36,8 @@ RSpec.describe 'API Markers' do it 'creates a marker', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 69_420 end @@ -47,6 +51,8 @@ RSpec.describe 'API Markers' do it 'updates a marker', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 70_120 end @@ -61,6 +67,8 @@ RSpec.describe 'API Markers' do it 'returns error json' do expect(response) .to have_http_status(409) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include(error: /Conflict during update/) end diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index d0af334825..d7d0b92f11 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -17,15 +17,12 @@ RSpec.describe 'Media' do it_behaves_like 'forbidden for wrong scope', 'read' - it 'returns http success' do + it 'returns http success with media information' do subject expect(response).to have_http_status(200) - end - - it 'returns the media information' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( id: media.id.to_s, @@ -44,6 +41,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(206) + expect(response.content_type) + .to start_with('application/json') end end @@ -54,6 +53,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -64,6 +65,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -80,6 +83,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(MediaAttachment.first).to be_present expect(MediaAttachment.first).to have_attached_file(:file) @@ -107,6 +112,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -117,6 +124,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(500) + expect(response.content_type) + .to start_with('application/json') end end end @@ -158,6 +167,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -176,6 +187,8 @@ RSpec.describe 'Media' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb index 6402c908ff..61e32cb9ae 100644 --- a/spec/requests/api/v1/mutes_spec.rb +++ b/spec/requests/api/v1/mutes_spec.rb @@ -18,32 +18,26 @@ RSpec.describe 'Mutes' do it_behaves_like 'forbidden for wrong scope', 'write write:mutes' - it 'returns http success' do + it 'returns http success with muted accounts' do subject expect(response).to have_http_status(200) - end - - it 'returns the muted accounts' do - subject + expect(response.content_type) + .to start_with('application/json') muted_accounts = mutes.map(&:target_account) - expect(response.parsed_body.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s }) end context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of muted accounts' do + it 'returns only the requested number of muted accounts with pagination headers' do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id), @@ -81,6 +75,8 @@ RSpec.describe 'Mutes' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/notifications/policies_spec.rb b/spec/requests/api/v1/notifications/policies_spec.rb index 8bafcad2fe..ac24501526 100644 --- a/spec/requests/api/v1/notifications/policies_spec.rb +++ b/spec/requests/api/v1/notifications/policies_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'Policies' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( filter_not_following: false, filter_not_followers: false, @@ -54,6 +56,8 @@ RSpec.describe 'Policies' do .to change { NotificationPolicy.find_or_initialize_by(account: user.account).for_not_following.to_sym }.from(:accept).to(:filter) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( filter_not_following: true, filter_not_followers: false, diff --git a/spec/requests/api/v1/notifications/requests_spec.rb b/spec/requests/api/v1/notifications/requests_spec.rb index dc125bc7aa..bee9d3a3da 100644 --- a/spec/requests/api/v1/notifications/requests_spec.rb +++ b/spec/requests/api/v1/notifications/requests_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'Requests' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -39,15 +41,12 @@ RSpec.describe 'Requests' do it_behaves_like 'forbidden for wrong scope', 'read read:notifications' - it 'returns http success' do + it 'returns http success and creates notification permission' do subject expect(response).to have_http_status(200) - end - - it 'creates notification permission' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil end @@ -58,6 +57,8 @@ RSpec.describe 'Requests' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -75,6 +76,8 @@ RSpec.describe 'Requests' do expect { subject }.to change(NotificationRequest, :count).by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification request belongs to someone else' do @@ -84,6 +87,8 @@ RSpec.describe 'Requests' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -102,6 +107,8 @@ RSpec.describe 'Requests' do expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -118,6 +125,8 @@ RSpec.describe 'Requests' do expect { subject }.to change(NotificationRequest, :count).by(-1) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -133,6 +142,8 @@ RSpec.describe 'Requests' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ merged: true }) end end @@ -146,6 +157,8 @@ RSpec.describe 'Requests' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ merged: false }) end end diff --git a/spec/requests/api/v1/notifications_spec.rb b/spec/requests/api/v1/notifications_spec.rb index b74adb5dff..0e8eb6ad3b 100644 --- a/spec/requests/api/v1/notifications_spec.rb +++ b/spec/requests/api/v1/notifications_spec.rb @@ -31,6 +31,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 5 end end @@ -45,6 +47,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -56,6 +60,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 4 end end @@ -67,6 +73,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -80,6 +88,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq Api::V1::NotificationsController::DEFAULT_NOTIFICATIONS_COUNT_LIMIT end end @@ -111,6 +121,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 5 expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') expect(response.parsed_body.any? { |x| x[:filtered] }).to be false @@ -124,6 +136,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 6 expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') expect(response.parsed_body.any? { |x| x[:filtered] }).to be true @@ -137,6 +151,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_account_ids.uniq).to eq [tom.account.id.to_s] end @@ -152,6 +168,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq 0 end end @@ -163,6 +181,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not eq 0 expect(body_json_types.uniq).to_not include 'mention' end @@ -175,6 +195,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types.uniq).to eq ['mention'] end end @@ -216,6 +238,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification belongs to someone else' do @@ -225,6 +249,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -242,6 +268,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end @@ -252,6 +280,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -272,6 +302,8 @@ RSpec.describe 'Notifications' do expect(user.account.reload.notifications).to be_empty expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/peers/search_spec.rb b/spec/requests/api/v1/peers/search_spec.rb index dc5f550d0e..d00a2437f2 100644 --- a/spec/requests/api/v1/peers/search_spec.rb +++ b/spec/requests/api/v1/peers/search_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'API Peers Search' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_blank end @@ -34,6 +36,8 @@ RSpec.describe 'API Peers Search' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_blank end @@ -49,6 +53,8 @@ RSpec.describe 'API Peers Search' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(1) expect(response.parsed_body.first) diff --git a/spec/requests/api/v1/polls/votes_spec.rb b/spec/requests/api/v1/polls/votes_spec.rb index 669f64b6e4..d3f7eb431d 100644 --- a/spec/requests/api/v1/polls/votes_spec.rb +++ b/spec/requests/api/v1/polls/votes_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'API V1 Polls Votes' do it 'creates a vote', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(vote).to_not be_nil expect(vote.choice).to eq 1 @@ -30,6 +32,8 @@ RSpec.describe 'API V1 Polls Votes' do it 'returns http bad request' do expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb index 138a37a73c..fd38297931 100644 --- a/spec/requests/api/v1/polls_spec.rb +++ b/spec/requests/api/v1/polls_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'Polls' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( id: poll.id.to_s, @@ -41,6 +43,8 @@ RSpec.describe 'Polls' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/preferences_spec.rb b/spec/requests/api/v1/preferences_spec.rb index d6991ca90c..e03b9cf108 100644 --- a/spec/requests/api/v1/preferences_spec.rb +++ b/spec/requests/api/v1/preferences_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Preferences' do expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -34,6 +36,9 @@ RSpec.describe 'Preferences' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index 26a9b848e5..fd3ab4bf58 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -28,31 +28,16 @@ RSpec.describe 'Deleting profile images' do it_behaves_like 'forbidden for wrong scope', 'read' end - it 'returns http success' do + it 'returns http success and deletes the avatar, preserves the header, queues up distribution' do delete '/api/v1/profile/avatar', headers: headers expect(response).to have_http_status(200) - end - - it 'deletes the avatar' do - delete '/api/v1/profile/avatar', headers: headers + expect(response.content_type) + .to start_with('application/json') account.reload - expect(account.avatar).to_not exist - end - - it 'does not delete the header' do - delete '/api/v1/profile/avatar', headers: headers - - account.reload - expect(account.header).to exist - end - - it 'queues up an account update distribution' do - delete '/api/v1/profile/avatar', headers: headers - expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) end end @@ -66,31 +51,16 @@ RSpec.describe 'Deleting profile images' do it_behaves_like 'forbidden for wrong scope', 'read' end - it 'returns http success' do + it 'returns http success, preserves the avatar, deletes the header, queues up distribution' do delete '/api/v1/profile/header', headers: headers expect(response).to have_http_status(200) - end - - it 'does not delete the avatar' do - delete '/api/v1/profile/header', headers: headers + expect(response.content_type) + .to start_with('application/json') account.reload - expect(account.avatar).to exist - end - - it 'deletes the header' do - delete '/api/v1/profile/header', headers: headers - - account.reload - expect(account.header).to_not exist - end - - it 'queues up an account update distribution' do - delete '/api/v1/profile/header', headers: headers - expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) end end diff --git a/spec/requests/api/v1/push/subscriptions_spec.rb b/spec/requests/api/v1/push/subscriptions_spec.rb index a9587f8d58..8ad672c95e 100644 --- a/spec/requests/api/v1/push/subscriptions_spec.rb +++ b/spec/requests/api/v1/push/subscriptions_spec.rb @@ -45,6 +45,8 @@ RSpec.describe 'API V1 Push Subscriptions' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(endpoint_push_subscriptions.count).to eq(0) expect(endpoint_push_subscription).to be_nil end diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb index a176bd78a6..18b894bf63 100644 --- a/spec/requests/api/v1/reports_spec.rb +++ b/spec/requests/api/v1/reports_spec.rb @@ -37,6 +37,8 @@ RSpec.describe 'Reports' do emails = capture_emails { subject } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match( a_hash_including( status_ids: [status.id.to_s], @@ -65,6 +67,8 @@ RSpec.describe 'Reports' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/scheduled_status_spec.rb b/spec/requests/api/v1/scheduled_status_spec.rb index eb03827c9a..3a1b81ce65 100644 --- a/spec/requests/api/v1/scheduled_status_spec.rb +++ b/spec/requests/api/v1/scheduled_status_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Scheduled Statuses' do expect(response) .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -33,6 +35,8 @@ RSpec.describe 'Scheduled Statuses' do expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -45,6 +49,8 @@ RSpec.describe 'Scheduled Statuses' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to_not be_present @@ -59,6 +65,8 @@ RSpec.describe 'Scheduled Statuses' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to be_present diff --git a/spec/requests/api/v1/statuses/bookmarks_spec.rb b/spec/requests/api/v1/statuses/bookmarks_spec.rb index f1bcfda0ff..583f5b6a0e 100644 --- a/spec/requests/api/v1/statuses/bookmarks_spec.rb +++ b/spec/requests/api/v1/statuses/bookmarks_spec.rb @@ -18,15 +18,13 @@ RSpec.describe 'Bookmarks' do it_behaves_like 'forbidden for wrong scope', 'read' context 'with public status' do - it 'bookmarks the status successfully', :aggregate_failures do + it 'bookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be true - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: true) @@ -41,6 +39,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -55,6 +55,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be true end end @@ -64,6 +66,8 @@ RSpec.describe 'Bookmarks' do post '/api/v1/statuses/-1/bookmark', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -74,6 +78,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -93,15 +99,13 @@ RSpec.describe 'Bookmarks' do Bookmark.find_or_create_by!(account: user.account, status: status) end - it 'unbookmarks the status successfully', :aggregate_failures do + it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: false) @@ -117,15 +121,13 @@ RSpec.describe 'Bookmarks' do status.account.block!(user.account) end - it 'unbookmarks the status successfully', :aggregate_failures do + it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, bookmarked: false) @@ -138,6 +140,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -149,6 +153,8 @@ RSpec.describe 'Bookmarks' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb b/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb index 24bd03d343..6471697154 100644 --- a/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb +++ b/spec/requests/api/v1/statuses/favourited_by_accounts_spec.rb @@ -33,6 +33,8 @@ RSpec.describe 'API V1 Statuses Favourited by Accounts' do prev: api_v1_status_favourited_by_index_url(limit: 2, since_id: Favourite.last.id), next: api_v1_status_favourited_by_index_url(limit: 2, max_id: Favourite.first.id) ) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(2) @@ -72,6 +74,8 @@ RSpec.describe 'API V1 Statuses Favourited by Accounts' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -88,6 +92,8 @@ RSpec.describe 'API V1 Statuses Favourited by Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/favourites_spec.rb b/spec/requests/api/v1/statuses/favourites_spec.rb index f9f0ff6299..3d1021e29d 100644 --- a/spec/requests/api/v1/statuses/favourites_spec.rb +++ b/spec/requests/api/v1/statuses/favourites_spec.rb @@ -18,15 +18,13 @@ RSpec.describe 'Favourites', :inline_jobs do it_behaves_like 'forbidden for wrong scope', 'read read:favourites' context 'with public status' do - it 'favourites the status successfully', :aggregate_failures do + it 'favourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be true - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true) @@ -41,6 +39,8 @@ RSpec.describe 'Favourites', :inline_jobs do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -55,6 +55,8 @@ RSpec.describe 'Favourites', :inline_jobs do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be true end end @@ -66,6 +68,8 @@ RSpec.describe 'Favourites', :inline_jobs do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -84,16 +88,14 @@ RSpec.describe 'Favourites', :inline_jobs do FavouriteService.new.call(user.account, status) end - it 'unfavourites the status successfully', :aggregate_failures do + it 'unfavourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) @@ -107,16 +109,14 @@ RSpec.describe 'Favourites', :inline_jobs do status.account.block!(user.account) end - it 'unfavourites the status successfully', :aggregate_failures do + it 'unfavourites the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) @@ -129,6 +129,8 @@ RSpec.describe 'Favourites', :inline_jobs do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -139,6 +141,8 @@ RSpec.describe 'Favourites', :inline_jobs do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/histories_spec.rb b/spec/requests/api/v1/statuses/histories_spec.rb index 4115a52fa8..9c7f93d343 100644 --- a/spec/requests/api/v1/statuses/histories_spec.rb +++ b/spec/requests/api/v1/statuses/histories_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'API V1 Statuses Histories' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not be 0 end end diff --git a/spec/requests/api/v1/statuses/mutes_spec.rb b/spec/requests/api/v1/statuses/mutes_spec.rb index 69ae948852..55313482d6 100644 --- a/spec/requests/api/v1/statuses/mutes_spec.rb +++ b/spec/requests/api/v1/statuses/mutes_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'API V1 Statuses Mutes' do it 'creates a conversation mute', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to_not be_nil end end @@ -32,6 +34,8 @@ RSpec.describe 'API V1 Statuses Mutes' do it 'destroys the conversation mute', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil end end diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb index 56e60c6d36..05d8f570cc 100644 --- a/spec/requests/api/v1/statuses/pins_spec.rb +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -18,15 +18,13 @@ RSpec.describe 'Pins' do it_behaves_like 'forbidden for wrong scope', 'read read:accounts' context 'when the status is public' do - it 'pins the status successfully', :aggregate_failures do + it 'pins the status successfully and returns updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be true - end - - it 'return json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, pinned: true) @@ -41,6 +39,8 @@ RSpec.describe 'Pins' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be true end end @@ -52,6 +52,8 @@ RSpec.describe 'Pins' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -60,6 +62,8 @@ RSpec.describe 'Pins' do post '/api/v1/statuses/-1/pin', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -70,6 +74,8 @@ RSpec.describe 'Pins' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -86,15 +92,13 @@ RSpec.describe 'Pins' do Fabricate(:status_pin, status: status, account: user.account) end - it 'unpins the status successfully', :aggregate_failures do + it 'unpins the status successfully and includes updated json', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(user.account.pinned?(status)).to be false - end - - it 'return json with updated attributes' do - subject expect(response.parsed_body).to match( a_hash_including(id: status.id.to_s, pinned: false) @@ -107,6 +111,8 @@ RSpec.describe 'Pins' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -115,6 +121,8 @@ RSpec.describe 'Pins' do post '/api/v1/statuses/-1/unpin', headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -125,6 +133,8 @@ RSpec.describe 'Pins' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb b/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb index bd26c22f08..40457f6e89 100644 --- a/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb +++ b/spec/requests/api/v1/statuses/reblogged_by_accounts_spec.rb @@ -32,6 +32,8 @@ RSpec.describe 'API V1 Statuses Reblogged by Accounts' do prev: api_v1_status_reblogged_by_index_url(limit: 2, since_id: bob.statuses.first.id), next: api_v1_status_reblogged_by_index_url(limit: 2, max_id: alice.statuses.first.id) ) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size) .to eq(2) @@ -71,6 +73,8 @@ RSpec.describe 'API V1 Statuses Reblogged by Accounts' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -87,6 +91,8 @@ RSpec.describe 'API V1 Statuses Reblogged by Accounts' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/reblogs_spec.rb b/spec/requests/api/v1/statuses/reblogs_spec.rb index 8c7894d875..5e88690074 100644 --- a/spec/requests/api/v1/statuses/reblogs_spec.rb +++ b/spec/requests/api/v1/statuses/reblogs_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'API V1 Statuses Reblogs' do context 'with public status' do it 'reblogs the status', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 1 @@ -40,6 +42,8 @@ RSpec.describe 'API V1 Statuses Reblogs' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -55,6 +59,8 @@ RSpec.describe 'API V1 Statuses Reblogs' do it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 0 @@ -80,6 +86,8 @@ RSpec.describe 'API V1 Statuses Reblogs' do it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reblogs.count).to eq 0 @@ -103,6 +111,8 @@ RSpec.describe 'API V1 Statuses Reblogs' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/sources_spec.rb b/spec/requests/api/v1/statuses/sources_spec.rb index c23fa91b0d..b9c09f6721 100644 --- a/spec/requests/api/v1/statuses/sources_spec.rb +++ b/spec/requests/api/v1/statuses/sources_spec.rb @@ -22,6 +22,8 @@ RSpec.describe 'Sources' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: status.id.to_s, text: status.text, @@ -38,6 +40,8 @@ RSpec.describe 'Sources' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -52,6 +56,8 @@ RSpec.describe 'Sources' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match({ id: status.id.to_s, text: status.text, @@ -68,6 +74,8 @@ RSpec.describe 'Sources' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index 047b2f0485..e316bd451b 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -20,6 +20,8 @@ RSpec.describe 'API V1 Statuses Translations' do it 'returns http unprocessable entity' do expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -38,6 +40,8 @@ RSpec.describe 'API V1 Statuses Translations' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 057800a266..ddf5945d25 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -18,6 +18,8 @@ RSpec.describe '/api/v1/statuses' do get '/api/v1/statuses', headers: headers, params: { id: [status.id, other_status.id, 123_123] } expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to contain_exactly( hash_including(id: status.id.to_s), hash_including(id: other_status.id.to_s) @@ -39,6 +41,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when post includes filtered terms' do @@ -52,6 +56,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -75,6 +81,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -97,6 +105,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:reblog][:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -121,6 +131,8 @@ RSpec.describe '/api/v1/statuses' do get "/api/v1/statuses/#{status.id}/context", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -139,6 +151,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s end @@ -154,6 +168,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to match [{ id: bob.id.to_s, acct: bob.acct }] end end @@ -165,6 +181,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s end end @@ -179,6 +197,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(429) + expect(response.content_type) + .to start_with('application/json') expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq '0' end @@ -191,6 +211,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -202,6 +224,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end it 'creates a scheduled status' do @@ -215,6 +239,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') expect(account.scheduled_statuses).to be_empty end end @@ -235,6 +261,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Status.find_by(id: status.id)).to be_nil end end @@ -253,6 +281,8 @@ RSpec.describe '/api/v1/statuses' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(status.reload.text).to eq 'I am updated' end end @@ -267,6 +297,8 @@ RSpec.describe '/api/v1/statuses' do get "/api/v1/statuses/#{status.id}" expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -279,6 +311,8 @@ RSpec.describe '/api/v1/statuses' do get "/api/v1/statuses/#{status.id}/context" expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -291,6 +325,8 @@ RSpec.describe '/api/v1/statuses' do get "/api/v1/statuses/#{status.id}" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -303,6 +339,8 @@ RSpec.describe '/api/v1/statuses' do get "/api/v1/statuses/#{status.id}/context" expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/streaming_spec.rb b/spec/requests/api/v1/streaming_spec.rb index a1f64846cf..62b43d657a 100644 --- a/spec/requests/api/v1/streaming_spec.rb +++ b/spec/requests/api/v1/streaming_spec.rb @@ -17,6 +17,8 @@ RSpec.describe 'API V1 Streaming' do get '/api/v1/streaming' expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb index 8267bb92a0..0a32d8899b 100644 --- a/spec/requests/api/v1/suggestions_spec.rb +++ b/spec/requests/api/v1/suggestions_spec.rb @@ -23,15 +23,12 @@ RSpec.describe 'Suggestions' do it_behaves_like 'forbidden for wrong scope', 'write' - it 'returns http success' do + it 'returns http success with accounts' do subject expect(response).to have_http_status(200) - end - - it 'returns accounts' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly(include(id: bob.id.to_s), include(id: jeff.id.to_s)) end @@ -53,6 +50,8 @@ RSpec.describe 'Suggestions' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -72,15 +71,12 @@ RSpec.describe 'Suggestions' do it_behaves_like 'forbidden for wrong scope', 'read' - it 'returns http success' do + it 'returns http success and removes suggestion' do subject expect(response).to have_http_status(200) - end - - it 'removes the specified suggestion' do - subject - + expect(response.content_type) + .to start_with('application/json') expect(FollowRecommendationMute.exists?(account: user.account, target_account: jeff)).to be true end diff --git a/spec/requests/api/v1/tags_spec.rb b/spec/requests/api/v1/tags_spec.rb index 9637823d45..f6ff7c614f 100644 --- a/spec/requests/api/v1/tags_spec.rb +++ b/spec/requests/api/v1/tags_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:name]).to eq(name) end end @@ -32,6 +34,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -42,6 +46,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -61,6 +67,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(:success) + expect(response.content_type) + .to start_with('application/json') expect(TagFollow.where(tag: tag, account: user.account)).to exist end end @@ -72,6 +80,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(Tag.where(name: name)).to exist expect(TagFollow.where(tag: Tag.find_by(name: name), account: user.account)).to exist end @@ -84,6 +94,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -95,6 +107,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end @@ -117,6 +131,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(TagFollow.where(tag: tag, account: user.account)).to_not exist end @@ -127,6 +143,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -138,6 +156,8 @@ RSpec.describe 'Tags' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb index afad2988ca..2023b189ec 100644 --- a/spec/requests/api/v1/timelines/home_spec.rb +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -31,14 +31,12 @@ RSpec.describe 'Home', :inline_jobs do PostStatusService.new.call(ana, text: 'New toot from ana.') end - it 'returns http success' do + it 'returns http success and statuses of followed users' do subject expect(response).to have_http_status(200) - end - - it 'returns the statuses of followed users' do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s }) end @@ -46,20 +44,18 @@ RSpec.describe 'Home', :inline_jobs do context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of statuses' do + it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do subject expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( prev: api_v1_timelines_home_url(limit: params[:limit], min_id: ana.statuses.first.id), next: api_v1_timelines_home_url(limit: params[:limit], max_id: ana.statuses.first.id) ) + expect(response.content_type) + .to start_with('application/json') end end end @@ -75,6 +71,8 @@ RSpec.describe 'Home', :inline_jobs do subject expect(response).to have_http_status(206) + expect(response.content_type) + .to start_with('application/json') end end @@ -85,6 +83,8 @@ RSpec.describe 'Home', :inline_jobs do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -97,6 +97,8 @@ RSpec.describe 'Home', :inline_jobs do expect(response) .to have_http_status(422) .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/link_spec.rb b/spec/requests/api/v1/timelines/link_spec.rb index d7fd1076fe..dc4d0d9506 100644 --- a/spec/requests/api/v1/timelines/link_spec.rb +++ b/spec/requests/api/v1/timelines/link_spec.rb @@ -13,6 +13,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) end end @@ -50,6 +52,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -62,6 +66,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -74,6 +80,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -91,6 +99,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -101,6 +111,8 @@ RSpec.describe 'Link' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -127,15 +139,13 @@ RSpec.describe 'Link' do context 'with limit param' do let(:params) { { limit: 1, url: url } } - it 'returns only the requested number of statuses', :aggregate_failures do + it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( diff --git a/spec/requests/api/v1/timelines/list_spec.rb b/spec/requests/api/v1/timelines/list_spec.rb index eb4395d1f9..1be754f264 100644 --- a/spec/requests/api/v1/timelines/list_spec.rb +++ b/spec/requests/api/v1/timelines/list_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'API V1 Timelines List' do get "/api/v1/timelines/list/#{list.id}", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -36,6 +38,8 @@ RSpec.describe 'API V1 Timelines List' do get "/api/v1/timelines/list/#{list.id}", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb index 53398b68ec..83d9039356 100644 --- a/spec/requests/api/v1/timelines/public_spec.rb +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -13,6 +13,8 @@ RSpec.describe 'Public' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) end end @@ -81,15 +83,13 @@ RSpec.describe 'Public' do context 'with limit param' do let(:params) { { limit: 1 } } - it 'returns only the requested number of statuses', :aggregate_failures do + it 'returns only the requested number of statuses and sets pagination headers', :aggregate_failures do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to eq(params[:limit]) - end - - it 'sets the correct pagination headers', :aggregate_failures do - subject expect(response) .to include_pagination_headers( @@ -114,6 +114,8 @@ RSpec.describe 'Public' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -124,6 +126,8 @@ RSpec.describe 'Public' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb index 554b5fd3e3..1d6844364d 100644 --- a/spec/requests/api/v1/timelines/tag_spec.rb +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -23,6 +23,8 @@ RSpec.describe 'Tag' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)) .to match_array(expected_statuses.map { |status| status.id.to_s }) .and not_include(private_status.id) @@ -85,6 +87,8 @@ RSpec.describe 'Tag' do prev: api_v1_timelines_tag_url(limit: params[:limit], min_id: love_status.id), next: api_v1_timelines_tag_url(limit: params[:limit], max_id: love_status.id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -111,6 +115,8 @@ RSpec.describe 'Tag' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/api/v1/trends/links_spec.rb b/spec/requests/api/v1/trends/links_spec.rb index 012d035907..b1b77e7fd8 100644 --- a/spec/requests/api/v1/trends/links_spec.rb +++ b/spec/requests/api/v1/trends/links_spec.rb @@ -10,7 +10,11 @@ RSpec.describe 'API V1 Trends Links' do it 'returns http success' do get '/api/v1/trends/links' - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) + .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -22,8 +26,11 @@ RSpec.describe 'API V1 Trends Links' do stub_const('Api::V1::Trends::LinksController::DEFAULT_LINKS_LIMIT', 2) get '/api/v1/trends/links' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_links_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/v1/trends/statuses_spec.rb b/spec/requests/api/v1/trends/statuses_spec.rb index 3b906e8f82..fe00c9c645 100644 --- a/spec/requests/api/v1/trends/statuses_spec.rb +++ b/spec/requests/api/v1/trends/statuses_spec.rb @@ -10,7 +10,11 @@ RSpec.describe 'API V1 Trends Statuses' do it 'returns http success' do get '/api/v1/trends/statuses' - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) + .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -22,8 +26,11 @@ RSpec.describe 'API V1 Trends Statuses' do stub_const('Api::BaseController::DEFAULT_STATUSES_LIMIT', 2) get '/api/v1/trends/statuses' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_statuses_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/v1/trends/tags_spec.rb b/spec/requests/api/v1/trends/tags_spec.rb index 598f4e7752..14ab73fc96 100644 --- a/spec/requests/api/v1/trends/tags_spec.rb +++ b/spec/requests/api/v1/trends/tags_spec.rb @@ -10,8 +10,11 @@ RSpec.describe 'API V1 Trends Tags' do it 'returns http success' do get '/api/v1/trends/tags' - expect(response).to have_http_status(200) - expect(response.headers).to_not include('Link') + expect(response) + .to have_http_status(200) + .and not_have_http_link_header + expect(response.content_type) + .to start_with('application/json') end end @@ -23,8 +26,11 @@ RSpec.describe 'API V1 Trends Tags' do stub_const('Api::V1::Trends::TagsController::DEFAULT_TAGS_LIMIT', 2) get '/api/v1/trends/tags' - expect(response).to have_http_status(200) - expect(response.headers).to include('Link') + expect(response) + .to have_http_status(200) + .and have_http_link_header(api_v1_trends_tags_url(offset: 2)).for(rel: 'next') + expect(response.content_type) + .to start_with('application/json') end def prepare_trends diff --git a/spec/requests/api/v2/admin/accounts_spec.rb b/spec/requests/api/v2/admin/accounts_spec.rb index 17c38e2e55..bc3db4f886 100644 --- a/spec/requests/api/v2/admin/accounts_spec.rb +++ b/spec/requests/api/v2/admin/accounts_spec.rb @@ -34,6 +34,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to eq([admin_account.id]) end end @@ -43,6 +45,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(remote_account.id) expect(body_json_ids).to_not include(other_remote_account.id) end @@ -53,6 +57,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(suspended_remote.id, suspended_account.id) end end @@ -62,6 +68,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(disabled_account.id) end end @@ -71,6 +79,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'returns the correct accounts' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_ids).to include(pending_account.id) end end @@ -85,6 +95,8 @@ RSpec.describe 'API V2 Admin Accounts' do it 'sets the correct pagination headers' do expect(response) .to include_pagination_headers(next: api_v2_admin_accounts_url(limit: 1, max_id: admin_account.id)) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters/keywords_spec.rb b/spec/requests/api/v2/filters/keywords_spec.rb index a31accaa5c..f0d62e9ecc 100644 --- a/spec/requests/api/v2/filters/keywords_spec.rb +++ b/spec/requests/api/v2/filters/keywords_spec.rb @@ -17,6 +17,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http success' do get "/api/v2/filters/#{filter.id}/keywords", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: keyword.id.to_s) @@ -27,6 +29,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http not found' do get "/api/v2/filters/#{other_filter.id}/keywords", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -41,6 +45,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -58,6 +64,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -72,6 +80,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'responds with the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -85,6 +95,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -99,6 +111,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'updates the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.keyword).to eq 'updated' end @@ -108,6 +122,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -122,6 +138,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'destroys the keyword', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -131,6 +149,8 @@ RSpec.describe 'API V2 Filters Keywords' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters/statuses_spec.rb b/spec/requests/api/v2/filters/statuses_spec.rb index aed8934a5e..f20a6ae5e6 100644 --- a/spec/requests/api/v2/filters/statuses_spec.rb +++ b/spec/requests/api/v2/filters/statuses_spec.rb @@ -17,6 +17,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'returns http success' do get "/api/v2/filters/#{filter.id}/statuses", headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to contain_exactly( include(id: status_filter.id.to_s) @@ -27,6 +29,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'returns http not found' do get "/api/v2/filters/#{other_filter.id}/statuses", headers: headers expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -42,6 +46,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -58,6 +64,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -72,6 +80,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'responds with the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -98,6 +108,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'destroys the filter', :aggregate_failures do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { status_filter.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -107,6 +119,8 @@ RSpec.describe 'API V2 Filters Statuses' do it 'returns http not found' do expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb index 850c773df3..3b5c44cefa 100644 --- a/spec/requests/api/v2/filters_spec.rb +++ b/spec/requests/api/v2/filters_spec.rb @@ -15,6 +15,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') end end @@ -32,6 +34,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s }) end end @@ -49,14 +53,12 @@ RSpec.describe 'Filters' do context 'with valid params' do let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } - it 'returns http success' do + it 'returns http success with a filter with keywords in json and creates a filter', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns a filter with keywords', :aggregate_failures do - subject + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( @@ -67,10 +69,6 @@ RSpec.describe 'Filters' do include(keyword: 'magic', whole_word: true) ) ) - end - - it 'creates a filter', :aggregate_failures do - subject filter = user.account.custom_filters.first @@ -89,6 +87,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -99,6 +99,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -109,6 +111,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -127,6 +131,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body) .to include( id: filter.id.to_s @@ -140,6 +146,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -166,6 +174,8 @@ RSpec.describe 'Filters' do filter.reload expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(filter.title).to eq 'updated' expect(filter.reload.context).to eq %w(home public) end @@ -178,6 +188,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end end @@ -189,20 +201,14 @@ RSpec.describe 'Filters' do allow(redis).to receive_messages(publish: nil) end - it 'returns http success' do + it 'returns http success and updates keyword and sends a filters_changed event' do subject expect(response).to have_http_status(200) - end - - it 'updates the keyword' do - subject + expect(response.content_type) + .to start_with('application/json') expect(keyword.reload.keyword).to eq 'updated' - end - - it 'sends exactly one filters_changed event' do - subject expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once end @@ -215,6 +221,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -229,14 +237,12 @@ RSpec.describe 'Filters' do it_behaves_like 'forbidden for wrong scope', 'read read:filters' it_behaves_like 'unauthorized for invalid token' - it 'returns http success' do + it 'returns http success and removes the filter' do subject expect(response).to have_http_status(200) - end - - it 'removes the filter' do - subject + expect(response.content_type) + .to start_with('application/json') expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound end @@ -248,6 +254,8 @@ RSpec.describe 'Filters' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/instance_spec.rb b/spec/requests/api/v2/instance_spec.rb index 6f7519ad1e..85d92f68d0 100644 --- a/spec/requests/api/v2/instance_spec.rb +++ b/spec/requests/api/v2/instance_spec.rb @@ -15,6 +15,9 @@ RSpec.describe 'Instances' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and include(title: 'Mastodon Glitch Edition') @@ -30,6 +33,9 @@ RSpec.describe 'Instances' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_present .and include(title: 'Mastodon Glitch Edition') diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb index 06ce0053e8..70e0679f57 100644 --- a/spec/requests/api/v2/media_spec.rb +++ b/spec/requests/api/v2/media_spec.rb @@ -21,6 +21,9 @@ RSpec.describe 'Media API', :attachment_processing do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) end @@ -38,6 +41,9 @@ RSpec.describe 'Media API', :attachment_processing do expect(response) .to have_http_status(202) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) end @@ -63,6 +69,9 @@ RSpec.describe 'Media API', :attachment_processing do expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) .and include(error: /File type/) @@ -80,6 +89,9 @@ RSpec.describe 'Media API', :attachment_processing do expect(response) .to have_http_status(500) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) .to be_a(Hash) .and include(error: /processing/) diff --git a/spec/requests/api/v2/notifications/accounts_spec.rb b/spec/requests/api/v2/notifications/accounts_spec.rb index 102b009c0b..29880a4c0a 100644 --- a/spec/requests/api/v2/notifications/accounts_spec.rb +++ b/spec/requests/api/v2/notifications/accounts_spec.rb @@ -30,6 +30,8 @@ RSpec.describe 'Accounts in grouped notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') # The group we are interested in is only favorites notifications = user.account.notifications.where(type: 'favourite').reorder(id: :desc) @@ -55,6 +57,8 @@ RSpec.describe 'Accounts in grouped notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') # The group we are interested in is only favorites notifications = user.account.notifications.where(type: 'favourite').reorder(id: :desc) diff --git a/spec/requests/api/v2/notifications/policies_spec.rb b/spec/requests/api/v2/notifications/policies_spec.rb index dc205b6ebb..f080bc730f 100644 --- a/spec/requests/api/v2/notifications/policies_spec.rb +++ b/spec/requests/api/v2/notifications/policies_spec.rb @@ -26,6 +26,8 @@ RSpec.describe 'Policies' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( for_not_following: 'accept', for_not_followers: 'accept', @@ -56,6 +58,8 @@ RSpec.describe 'Policies' do .and change { NotificationPolicy.find_or_initialize_by(account: user.account).for_limited_accounts.to_sym }.from(:filter).to(:drop) expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to include( for_not_following: 'filter', for_not_followers: 'accept', diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index edf333ecd8..ffa0a71c77 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -31,6 +31,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 4 end end @@ -42,6 +44,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 5 end end @@ -56,6 +60,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -67,6 +73,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 3 end end @@ -78,6 +86,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq 2 end end @@ -91,6 +101,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:count]).to eq Api::V2::NotificationsController::DEFAULT_NOTIFICATIONS_COUNT_LIMIT end end @@ -125,6 +137,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:notification_groups]).to eq [] end end @@ -134,6 +148,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types).to include('reblog', 'mention', 'favourite', 'follow') end end @@ -145,6 +161,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:notification_groups]).to contain_exactly( a_hash_including( type: 'reblog', @@ -177,6 +195,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body.size).to_not eq 0 expect(body_json_types.uniq).to_not include 'mention' end @@ -189,6 +209,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(body_json_types.uniq).to eq ['mention'] expect(response.parsed_body.dig(:notification_groups, 0, :page_min_id)).to_not be_nil end @@ -211,6 +233,8 @@ RSpec.describe 'Notifications' do # not the last that has been skipped, so pagination is very likely to give overlap next: api_v2_notifications_url(limit: params[:limit], max_id: notifications[3].id) ) + expect(response.content_type) + .to start_with('application/json') end end @@ -224,6 +248,8 @@ RSpec.describe 'Notifications' do expect(response.parsed_body[:notification_groups].size) .to eq(2) + expect(response.content_type) + .to start_with('application/json') expect(response) .to include_pagination_headers( prev: api_v2_notifications_url(limit: params[:limit], min_id: notifications.first.id), @@ -247,6 +273,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:partial_accounts].size).to be > 0 expect(response.parsed_body[:partial_accounts][0].keys.map(&:to_sym)).to contain_exactly(:acct, :avatar, :avatar_static, :bot, :id, :locked, :url) expect(response.parsed_body[:partial_accounts].pluck(:id)).to_not include(recent_account.id.to_s) @@ -261,6 +289,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -282,6 +312,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when notification belongs to someone else' do @@ -291,6 +323,8 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -308,16 +342,21 @@ RSpec.describe 'Notifications' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end context 'when notification belongs to someone else' do - let(:notification) { Fabricate(:notification) } + let(:notification) { Fabricate(:notification, group_key: 'foobar') } - it 'returns http not found' do - subject + it 'leaves the notification alone' do + expect { subject } + .to_not change(Notification, :count) - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end @@ -338,6 +377,8 @@ RSpec.describe 'Notifications' do expect(user.account.reload.notifications).to be_empty expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/v2/search_spec.rb b/spec/requests/api/v2/search_spec.rb index a59ec7ca6b..5a2346dc39 100644 --- a/spec/requests/api/v2/search_spec.rb +++ b/spec/requests/api/v2/search_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'when searching accounts' do @@ -37,6 +39,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -47,6 +51,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -57,6 +63,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -67,6 +75,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -92,6 +102,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end @@ -102,6 +114,8 @@ RSpec.describe 'Search API' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -118,6 +132,8 @@ RSpec.describe 'Search API' do context 'without a `q` param' do it 'returns http bad_request' do expect(response).to have_http_status(400) + expect(response.content_type) + .to start_with('application/json') end end @@ -126,6 +142,8 @@ RSpec.describe 'Search API' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -134,6 +152,8 @@ RSpec.describe 'Search API' do it 'returns http success' do expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end context 'with truthy `resolve`' do @@ -141,6 +161,8 @@ RSpec.describe 'Search API' do it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') expect(response.body).to match('resolve remote resources') end end @@ -150,6 +172,8 @@ RSpec.describe 'Search API' do it 'returns http unauthorized' do expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') expect(response.body).to match('pagination is not supported') end end diff --git a/spec/requests/api/v2/suggestions_spec.rb b/spec/requests/api/v2/suggestions_spec.rb index e92507ed66..099d9bc3b2 100644 --- a/spec/requests/api/v2/suggestions_spec.rb +++ b/spec/requests/api/v2/suggestions_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'Suggestions API' do get '/api/v2/suggestions', headers: headers expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body).to match_array( [bob, jeff].map do |account| diff --git a/spec/requests/api/web/embeds_spec.rb b/spec/requests/api/web/embeds_spec.rb index 2b28502835..3cc2f977f8 100644 --- a/spec/requests/api/web/embeds_spec.rb +++ b/spec/requests/api/web/embeds_spec.rb @@ -18,6 +18,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end end @@ -29,6 +31,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -42,6 +46,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -52,6 +58,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -71,6 +79,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end @@ -83,6 +93,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -98,6 +110,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -123,6 +137,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -133,6 +149,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') expect(response.parsed_body[:html]).to be_present end end @@ -146,6 +164,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end @@ -156,6 +176,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end @@ -167,6 +189,8 @@ RSpec.describe '/api/web/embed' do subject expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') end end end diff --git a/spec/requests/api/web/settings_spec.rb b/spec/requests/api/web/settings_spec.rb index 81b8b44953..3873bc179b 100644 --- a/spec/requests/api/web/settings_spec.rb +++ b/spec/requests/api/web/settings_spec.rb @@ -17,6 +17,8 @@ RSpec.describe '/api/web/settings' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') end end @@ -29,6 +31,8 @@ RSpec.describe '/api/web/settings' do expect(response) .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') end end diff --git a/spec/requests/auth/sessions/security_key_options_spec.rb b/spec/requests/auth/sessions/security_key_options_spec.rb new file mode 100644 index 0000000000..6246e4beb3 --- /dev/null +++ b/spec/requests/auth/sessions/security_key_options_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'webauthn/fake_client' + +RSpec.describe 'Security Key Options' do + describe 'GET /auth/sessions/security_key_options' do + let!(:user) do + Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + end + + context 'with WebAuthn and OTP enabled as second factor' do + let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } + + let(:fake_client) { WebAuthn::FakeClient.new(domain) } + let(:public_key_credential) { WebAuthn::Credential.from_create(fake_client.create) } + + before do + user.update(webauthn_id: WebAuthn.generate_user_id) + Fabricate( + :webauthn_credential, + user_id: user.id, + external_id: public_key_credential.id, + public_key: public_key_credential.public_key + ) + post '/auth/sign_in', params: { user: { email: user.email, password: user.password } } + end + + it 'returns http success' do + get '/auth/sessions/security_key_options' + + expect(response) + .to have_http_status 200 + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when WebAuthn not enabled' do + it 'returns http unauthorized' do + get '/auth/sessions/security_key_options' + + expect(response) + .to have_http_status 401 + expect(response.content_type) + .to start_with('application/json') + end + end + end +end diff --git a/spec/requests/settings/exports_spec.rb b/spec/requests/settings/exports_spec.rb new file mode 100644 index 0000000000..db823ac770 --- /dev/null +++ b/spec/requests/settings/exports_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Settings / Exports' do + context 'when not signed in' do + describe 'GET /settings/export' do + it 'redirects to sign in page' do + get settings_export_path + + expect(response) + .to redirect_to new_user_session_path + end + end + + describe 'POST /settings/export' do + it 'redirects to sign in page' do + post settings_export_path + + expect(response) + .to redirect_to new_user_session_path + end + end + end +end diff --git a/spec/requests/severed_relationships_spec.rb b/spec/requests/severed_relationships_spec.rb new file mode 100644 index 0000000000..ac98ab8f94 --- /dev/null +++ b/spec/requests/severed_relationships_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Severed Relationships' do + let(:account_rs_event) { Fabricate :account_relationship_severance_event } + + before { sign_in Fabricate(:user) } + + describe 'GET /severed_relationships/:id/following' do + it 'returns a CSV file with correct data' do + get following_severed_relationship_path(account_rs_event, format: :csv) + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('text/csv') + expect(response.headers['Content-Disposition']) + .to match(<<~FILENAME.squish) + attachment; filename="following-example.com-#{Date.current}.csv" + FILENAME + expect(response.body) + .to include('Account address') + end + end + + describe 'GET /severed_relationships/:id/followers' do + it 'returns a CSV file with correct data' do + get followers_severed_relationship_path(account_rs_event, format: :csv) + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('text/csv') + expect(response.headers['Content-Disposition']) + .to match(<<~FILENAME.squish) + attachment; filename="followers-example.com-#{Date.current}.csv" + FILENAME + expect(response.body) + .to include('Account address') + end + end +end diff --git a/spec/serializers/activitypub/device_serializer_spec.rb b/spec/serializers/activitypub/device_serializer_spec.rb deleted file mode 100644 index 226e136446..0000000000 --- a/spec/serializers/activitypub/device_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::DeviceSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:device) } - - describe 'type' do - it 'returns correct serialized type' do - expect(serialization['type']).to eq('Device') - end - end -end diff --git a/spec/serializers/activitypub/one_time_key_serializer_spec.rb b/spec/serializers/activitypub/one_time_key_serializer_spec.rb deleted file mode 100644 index b9792ebae3..0000000000 --- a/spec/serializers/activitypub/one_time_key_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::OneTimeKeySerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:one_time_key) } - - describe 'type' do - it 'returns correct serialized type' do - expect(serialization['type']).to eq('Curve25519Key') - end - end -end diff --git a/spec/serializers/rest/encrypted_message_serializer_spec.rb b/spec/serializers/rest/encrypted_message_serializer_spec.rb deleted file mode 100644 index a4b8ee83b2..0000000000 --- a/spec/serializers/rest/encrypted_message_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::EncryptedMessageSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Fabricate(:encrypted_message) } - - describe 'account' do - it 'returns the associated account' do - expect(serialization['account_id']).to eq(record.from_account.id.to_s) - end - end -end diff --git a/spec/serializers/rest/keys/claim_result_serializer_spec.rb b/spec/serializers/rest/keys/claim_result_serializer_spec.rb deleted file mode 100644 index e45112705b..0000000000 --- a/spec/serializers/rest/keys/claim_result_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::ClaimResultSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Keys::ClaimService::Result.new(Account.new(id: 123), 456) } - - describe 'account' do - it 'returns the associated account' do - expect(serialization['account_id']).to eq('123') - end - end -end diff --git a/spec/serializers/rest/keys/device_serializer_spec.rb b/spec/serializers/rest/keys/device_serializer_spec.rb deleted file mode 100644 index b8370beac7..0000000000 --- a/spec/serializers/rest/keys/device_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::DeviceSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Device.new(name: 'Device name') } - - describe 'name' do - it 'returns the name' do - expect(serialization['name']).to eq('Device name') - end - end -end diff --git a/spec/serializers/rest/keys/query_result_serializer_spec.rb b/spec/serializers/rest/keys/query_result_serializer_spec.rb deleted file mode 100644 index 41492f5e78..0000000000 --- a/spec/serializers/rest/keys/query_result_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe REST::Keys::QueryResultSerializer do - let(:serialization) { serialized_record_json(record, described_class) } - let(:record) { Keys::QueryService::Result.new(Account.new(id: 123), []) } - - describe 'account' do - it 'returns the associated account id' do - expect(serialization['account_id']).to eq('123') - end - end -end diff --git a/spec/support/examples/models/concerns/reviewable.rb b/spec/support/examples/models/concerns/reviewable.rb index b63e44b43f..144019d969 100644 --- a/spec/support/examples/models/concerns/reviewable.rb +++ b/spec/support/examples/models/concerns/reviewable.rb @@ -6,6 +6,31 @@ RSpec.shared_examples 'Reviewable' do let(:reviewed_at) { nil } let(:requested_review_at) { nil } + describe 'Scopes' do + let!(:reviewed_record) { Fabricate factory_name, reviewed_at: 10.days.ago } + let!(:un_reviewed_record) { Fabricate factory_name, reviewed_at: nil } + + describe '.reviewed' do + it 'returns reviewed records' do + expect(described_class.reviewed) + .to include(reviewed_record) + .and not_include(un_reviewed_record) + end + end + + describe '.unreviewed' do + it 'returns non reviewed records' do + expect(described_class.unreviewed) + .to include(un_reviewed_record) + .and not_include(reviewed_record) + end + end + + def factory_name + described_class.name.underscore.to_sym + end + end + describe '#requires_review?' do it { is_expected.to be_requires_review } diff --git a/spec/system/settings/exports_spec.rb b/spec/system/settings/exports_spec.rb new file mode 100644 index 0000000000..2cc2961a0b --- /dev/null +++ b/spec/system/settings/exports_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Export page' do + let(:user) { Fabricate :user } + + before { sign_in user } + + describe 'Viewing the export page' do + context 'when signed in' do + it 'shows the export page', :aggregate_failures do + visit settings_export_path + + expect(page) + .to have_content(takeout_summary) + .and have_private_cache_control + end + end + end + + describe 'Creating a new archive' do + it 'queues a worker and redirects' do + visit settings_export_path + + expect { request_archive } + .to change(BackupWorker.jobs, :size).by(1) + expect(page) + .to have_content(takeout_summary) + end + + def request_archive + click_on I18n.t('exports.archive_takeout.request') + end + end + + def takeout_summary + I18n.t('settings.export') + end +end diff --git a/spec/workers/push_encrypted_message_worker_spec.rb b/spec/workers/push_encrypted_message_worker_spec.rb deleted file mode 100644 index 311545cf56..0000000000 --- a/spec/workers/push_encrypted_message_worker_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe PushEncryptedMessageWorker do - let(:worker) { described_class.new } - - describe 'perform' do - it 'runs without error for missing record' do - expect { worker.perform(nil) }.to_not raise_error - end - end -end diff --git a/streaming/index.js b/streaming/index.js index f4aeadf23f..d792f95acf 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -44,7 +44,6 @@ initializeLogLevel(process.env, environment); * @property {string[]} scopes * @property {string} accountId * @property {string[]} chosenLanguages - * @property {string} deviceId */ @@ -355,7 +354,7 @@ const startServer = async () => { * @returns {Promise} */ const accountFromToken = async (token, req) => { - const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes, devices.device_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id LEFT OUTER JOIN devices ON oauth_access_tokens.id = devices.access_token_id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token]); + const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token]); if (result.rows.length === 0) { throw new AuthenticationError('Invalid access token'); @@ -365,14 +364,12 @@ const startServer = async () => { req.scopes = result.rows[0].scopes.split(' '); req.accountId = result.rows[0].account_id; req.chosenLanguages = result.rows[0].chosen_languages; - req.deviceId = result.rows[0].device_id; return { accessTokenId: result.rows[0].id, scopes: result.rows[0].scopes.split(' '), accountId: result.rows[0].account_id, chosenLanguages: result.rows[0].chosen_languages, - deviceId: result.rows[0].device_id }; }; @@ -990,10 +987,6 @@ const startServer = async () => { const channelsForUserStream = req => { const arr = [`timeline:${req.accountId}`]; - if (isInScope(req, ['crypto']) && req.deviceId) { - arr.push(`timeline:${req.accountId}:${req.deviceId}`); - } - if (isInScope(req, ['read', 'read:notifications'])) { arr.push(`timeline:${req.accountId}:notifications`); } diff --git a/yarn.lock b/yarn.lock index c44e210015..5de514cc5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5179,17 +5179,17 @@ __metadata: linkType: hard "babel-loader@npm:^8.3.0": - version: 8.3.0 - resolution: "babel-loader@npm:8.3.0" + version: 8.4.1 + resolution: "babel-loader@npm:8.4.1" dependencies: find-cache-dir: "npm:^3.3.1" - loader-utils: "npm:^2.0.0" + loader-utils: "npm:^2.0.4" make-dir: "npm:^3.1.0" schema-utils: "npm:^2.6.5" peerDependencies: "@babel/core": ^7.0.0 webpack: ">=2" - checksum: 10c0/7b83bae35a12fbc5cdf250e2d36a288305fe5b6d20ab044ab7c09bbf456c8895b80af7a4f1e8b64b5c07a4fd48d4b5144dab40b4bc72a4fed532dc000362f38f + checksum: 10c0/efdca9c3ef502af58b923a32123d660c54fd0be125b7b64562c8a43bda0a3a55dac0db32331674104e7e5184061b75c3a0e395b2c5ccdc7cb2125dd9ec7108d2 languageName: node linkType: hard @@ -5909,25 +5909,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.1": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 - languageName: node - linkType: hard - "chokidar@npm:^2.1.8": version: 2.1.8 resolution: "chokidar@npm:2.1.8" @@ -5951,6 +5932,34 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.4.1": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.0": + version: 4.0.0 + resolution: "chokidar@npm:4.0.0" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/42d03c53b0ad200689e4fae7763133561480561cab8ba5304e8f2298ff45ff84bf0f6065c3f02b9e557b74b156813734439a1a2ff19a1ea6b35692395cd92738 + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -11445,7 +11454,7 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^2.0.0": +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.4": version: 2.0.4 resolution: "loader-utils@npm:2.0.4" dependencies: @@ -13016,10 +13025,10 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.6.4": - version: 2.6.4 - resolution: "pg-connection-string@npm:2.6.4" - checksum: 10c0/0d0b617df0fc6507bf6a94bdcd56c7a305788a1402d69bff9773350947c8f525d6d8136128065370749a3325e99658ae40fbdcce620fb8e60126181f0591a6a6 +"pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.7.0": + version: 2.7.0 + resolution: "pg-connection-string@npm:2.7.0" + checksum: 10c0/50a1496a1c858f9495d78a2c7a66d93ef3602e718aff2953bb5738f3ea616d7f727f32fc20513c9bed127650cd14c1ddc7b458396f4000e689d4b64c65c5c51e languageName: node linkType: hard @@ -13037,19 +13046,19 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.6.2": - version: 3.6.2 - resolution: "pg-pool@npm:3.6.2" +"pg-pool@npm:^3.7.0": + version: 3.7.0 + resolution: "pg-pool@npm:3.7.0" peerDependencies: pg: ">=8.0" - checksum: 10c0/14c524549490954b5e48457a4b808df8f619f6deeb3b395b0cd184a8f4ed65a9273fe0697ba0341a41d6745af197f1437eb1cf51fff0cbbf5b0fb3852ebe5392 + checksum: 10c0/9128673cf941f288c0cb1a74ca959a9b4f6075ef73b2cc7dece5d4db3dd7043784869e7c12bce2e69ca0df22132a419cc45c2050b4373632856fe8bae9eb94b5 languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.6.1": - version: 1.6.1 - resolution: "pg-protocol@npm:1.6.1" - checksum: 10c0/7eadef4010ac0a3925c460be7332ca4098a5c6d5181725a62193fcfa800000ae6632d98d814f3989b42cf5fdc3b45e34c714a1959d29174e81e30730e140ae5f +"pg-protocol@npm:*, pg-protocol@npm:^1.7.0": + version: 1.7.0 + resolution: "pg-protocol@npm:1.7.0" + checksum: 10c0/c4af854d9b843c808231c0040fed89f2b9101006157df8da2bb2f62a7dde702de748d852228dc22df41cc7ffddfb526af3bcb34b278b581e9f76a060789186c1 languageName: node linkType: hard @@ -13082,13 +13091,13 @@ __metadata: linkType: hard "pg@npm:^8.5.0": - version: 8.12.0 - resolution: "pg@npm:8.12.0" + version: 8.13.0 + resolution: "pg@npm:8.13.0" dependencies: pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.6.4" - pg-pool: "npm:^3.6.2" - pg-protocol: "npm:^1.6.1" + pg-connection-string: "npm:^2.7.0" + pg-pool: "npm:^3.7.0" + pg-protocol: "npm:^1.7.0" pg-types: "npm:^2.1.0" pgpass: "npm:1.x" peerDependencies: @@ -13099,7 +13108,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10c0/973e49b5e7327c42fc62806efa8c824159ab7a0b676cefe6eeb51a59b6e226587911ec27697f36c18d69e58a7f4f0b76d0829364087194d13ed431ab7c9c417a + checksum: 10c0/1521189063d2293d62f3fac61e797a3096a62a69668c223827d00b83c17a320805f31f0b5316feb80f8d9eed0c6c32f95146d8aca866af05816a66fd2ba8e32a languageName: node linkType: hard @@ -14704,8 +14713,8 @@ __metadata: linkType: hard "react-select@npm:^5.7.3": - version: 5.8.0 - resolution: "react-select@npm:5.8.0" + version: 5.8.1 + resolution: "react-select@npm:5.8.1" dependencies: "@babel/runtime": "npm:^7.12.0" "@emotion/cache": "npm:^11.4.0" @@ -14719,7 +14728,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/b4b98aaf117ee5cc4642871b7bd51fd0e2697988d0b880f30b21e933ca90258959147117d8aada36713b622e0e4cb06bd18ec02069f3f108896e0d31e69e3c16 + checksum: 10c0/0fd73e1e472105f980e09c86f0e6adbdc9f2f5c1befa275b08c71653becdd1829f596155a81b5085cb86f18b20bf4f4cc439ab5fe23e68f326e169dcfe00ccf6 languageName: node linkType: hard @@ -14908,6 +14917,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.0.1 + resolution: "readdirp@npm:4.0.1" + checksum: 10c0/e5a0b547015f68ecc918f115b62b75b2b840611480a9240cb3317090a0ddac01bb9b40315a8fa08acdf52a43eea17b808c89b645263cba3ab64dc557d7f801f1 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -15483,15 +15499,15 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.78.0 - resolution: "sass@npm:1.78.0" + version: 1.79.2 + resolution: "sass@npm:1.79.2" dependencies: - chokidar: "npm:>=3.0.0 <4.0.0" + chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10c0/6577a87c00b03a5a50f3a11b4b6592f28abce34e61812e381535a3b712151bd94db3ca06467d20395431e0f38a23f99e616d6859d771fb6d4617c359f590c48c + checksum: 10c0/b637daf133da4fbafbb7e6ae07b01ff7c73e406f3134e66749bf6f712dcc0056c6971d8629d8cc2b186df5ffb2282baa8f1818f35e326b3558ab284e31fdd87d languageName: node linkType: hard