Merge commit 'e387175fc9a3ebfd72ab45ebfe43ecfabef7b0c3' into glitch-soc/merge-upstream

pull/62/head
Claire 2023-05-25 23:47:28 +02:00
commit e2ab9d4dad
46 changed files with 570 additions and 303 deletions

View File

@ -239,31 +239,6 @@ Naming/VariableNumber:
- 'spec/models/user_spec.rb' - 'spec/models/user_spec.rb'
- 'spec/services/activitypub/fetch_featured_collection_service_spec.rb' - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Performance/MapCompact:
Exclude:
- 'app/lib/admin/metrics/dimension.rb'
- 'app/lib/admin/metrics/measure.rb'
- 'app/lib/feed_manager.rb'
- 'app/models/account.rb'
- 'app/models/account_statuses_cleanup_policy.rb'
- 'app/models/account_suggestions/setting_source.rb'
- 'app/models/account_suggestions/source.rb'
- 'app/models/follow_recommendation_filter.rb'
- 'app/models/notification.rb'
- 'app/models/user_role.rb'
- 'app/models/webhook.rb'
- 'app/services/process_mentions_service.rb'
- 'app/validators/existing_username_validator.rb'
- 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb'
- 'spec/presenters/status_relationships_presenter_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeMultiline.
Performance/StartWith:
Exclude:
- 'app/lib/extractor.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Performance/UnfreezeString: Performance/UnfreezeString:
Exclude: Exclude:
@ -599,10 +574,6 @@ RSpec/PredicateMatcher:
- 'spec/models/user_spec.rb' - 'spec/models/user_spec.rb'
- 'spec/services/post_status_service_spec.rb' - 'spec/services/post_status_service_spec.rb'
RSpec/RepeatedExample:
Exclude:
- 'spec/policies/status_policy_spec.rb'
RSpec/StubbedMock: RSpec/StubbedMock:
Exclude: Exclude:
- 'spec/controllers/api/base_controller_spec.rb' - 'spec/controllers/api/base_controller_spec.rb'

View File

@ -17,7 +17,7 @@ gem 'makara', '~> 0.5'
gem 'pghero' gem 'pghero'
gem 'dotenv-rails', '~> 2.8' gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.120', require: false gem 'aws-sdk-s3', '~> 1.122', require: false
gem 'fog-core', '<= 2.4.0' gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b'
@ -75,7 +75,7 @@ gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-s
gem 'redcarpet', '~> 3.6' gem 'redcarpet', '~> 3.6'
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 2.1' gem 'rqrcode', '~> 2.2'
gem 'ruby-progressbar', '~> 1.13' gem 'ruby-progressbar', '~> 1.13'
gem 'sanitize', '~> 6.0' gem 'sanitize', '~> 6.0'
gem 'scenic', '~> 1.7' gem 'scenic', '~> 1.7'

View File

@ -109,16 +109,16 @@ GEM
attr_required (1.0.1) attr_required (1.0.1)
awrence (1.2.1) awrence (1.2.1)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.752.0) aws-partitions (1.761.0)
aws-sdk-core (3.171.0) aws-sdk-core (3.172.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.63.0) aws-sdk-kms (1.64.0)
aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.121.0) aws-sdk-s3 (1.122.0)
aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4) aws-sigv4 (~> 1.4)
@ -189,7 +189,7 @@ GEM
coderay (1.1.3) coderay (1.1.3)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.2)
connection_pool (2.4.0) connection_pool (2.4.1)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0) openssl-signature_algorithm (~> 1.0)
@ -398,9 +398,9 @@ GEM
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.20.0) loofah (2.21.3)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.8.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
@ -576,7 +576,7 @@ GEM
rexml (3.2.5) rexml (3.2.5)
rotp (6.2.2) rotp (6.2.2)
rpam2 (4.0.2) rpam2 (4.0.2)
rqrcode (2.1.2) rqrcode (2.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
@ -588,20 +588,20 @@ GEM
rspec-mocks (3.12.5) rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-rails (6.0.1) rspec-rails (6.0.2)
actionpack (>= 6.1) actionpack (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
railties (>= 6.1) railties (>= 6.1)
rspec-core (~> 3.11) rspec-core (~> 3.12)
rspec-expectations (~> 3.11) rspec-expectations (~> 3.12)
rspec-mocks (~> 3.11) rspec-mocks (~> 3.12)
rspec-support (~> 3.11) rspec-support (~> 3.12)
rspec-sidekiq (3.1.0) rspec-sidekiq (3.1.0)
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.12.0) rspec-support (3.12.0)
rspec_chunked (0.6) rspec_chunked (0.6)
rubocop (1.50.2) rubocop (1.51.0)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.0.0) parser (>= 3.2.0.0)
@ -611,11 +611,11 @@ GEM
rubocop-ast (>= 1.28.0, < 2.0) rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.28.0) rubocop-ast (1.28.1)
parser (>= 3.2.1.0) parser (>= 3.2.1.0)
rubocop-capybara (2.18.0) rubocop-capybara (2.18.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-performance (1.17.1) rubocop-performance (1.18.0)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.19.1) rubocop-rails (2.19.1)
@ -761,7 +761,7 @@ GEM
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.7) zeitwerk (2.6.8)
PLATFORMS PLATFORMS
ruby ruby
@ -770,7 +770,7 @@ DEPENDENCIES
active_model_serializers (~> 0.10) active_model_serializers (~> 0.10)
addressable (~> 2.8) addressable (~> 2.8)
annotate (~> 3.2) annotate (~> 3.2)
aws-sdk-s3 (~> 1.120) aws-sdk-s3 (~> 1.122)
better_errors (~> 2.9) better_errors (~> 2.9)
binding_of_caller (~> 1.0) binding_of_caller (~> 1.0)
blurhash (~> 0.1) blurhash (~> 0.1)
@ -860,7 +860,7 @@ DEPENDENCIES
redcarpet (~> 3.6) redcarpet (~> 3.6)
redis (~> 4.5) redis (~> 4.5)
redis-namespace (~> 1.10) redis-namespace (~> 1.10)
rqrcode (~> 2.1) rqrcode (~> 2.2)
rspec-rails (~> 6.0) rspec-rails (~> 6.0)
rspec-sidekiq (~> 3.1) rspec-sidekiq (~> 3.1)
rspec_chunked (~> 0.6) rspec_chunked (~> 0.6)

View File

@ -13,7 +13,7 @@ class Api::V1::FeaturedTagsController < Api::BaseController
end end
def create def create
featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name]) featured_tag = CreateFeaturedTagService.new.call(current_account, params.require(:name))
render json: featured_tag, serializer: REST::FeaturedTagSerializer render json: featured_tag, serializer: REST::FeaturedTagSerializer
end end
@ -33,6 +33,6 @@ class Api::V1::FeaturedTagsController < Api::BaseController
end end
def featured_tag_params def featured_tag_params
params.permit(:name) params.require(:name)
end end
end end

View File

@ -4,7 +4,7 @@ import api from 'mastodon/api';
import { FormattedNumber } from 'react-intl'; import { FormattedNumber } from 'react-intl';
import { Sparklines, SparklinesCurve } from 'react-sparklines'; import { Sparklines, SparklinesCurve } from 'react-sparklines';
import classNames from 'classnames'; import classNames from 'classnames';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
const percIncrease = (a, b) => { const percIncrease = (a, b) => {
let percent; let percent;

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import api from 'mastodon/api'; import api from 'mastodon/api';
import { FormattedNumber } from 'react-intl'; import { FormattedNumber } from 'react-intl';
import { roundTo10 } from 'mastodon/utils/numbers'; import { roundTo10 } from 'mastodon/utils/numbers';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
export default class Dimension extends React.PureComponent { export default class Dimension extends React.PureComponent {

View File

@ -5,7 +5,7 @@ import type { List } from 'immutable';
import type { Account } from '../../types/resources'; import type { Account } from '../../types/resources';
import { autoPlayGif } from '../initial_state'; import { autoPlayGif } from '../initial_state';
import Skeleton from './skeleton'; import { Skeleton } from './skeleton';
interface Props { interface Props {
account?: Account; account?: Account;

View File

@ -3,7 +3,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { DisplayName } from 'mastodon/components/display_name'; import { DisplayName } from 'mastodon/components/display_name';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
interface Props { interface Props {
size?: number; size?: number;

View File

@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ShortNumber from 'mastodon/components/short_number'; import ShortNumber from 'mastodon/components/short_number';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
import classNames from 'classnames'; import classNames from 'classnames';
class SilentErrorBoundary extends React.Component { class SilentErrorBoundary extends React.Component {

View File

@ -4,7 +4,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchServer } from 'mastodon/actions/server'; import { fetchServer } from 'mastodon/actions/server';
import ShortNumber from 'mastodon/components/short_number'; import ShortNumber from 'mastodon/components/short_number';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
import Account from 'mastodon/containers/account_container'; import Account from 'mastodon/containers/account_container';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { ServerHeroImage } from 'mastodon/components/server_hero_image'; import { ServerHeroImage } from 'mastodon/components/server_hero_image';

View File

@ -1,11 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const Skeleton = ({ width, height }) => <span className='skeleton' style={{ width, height }}>&zwnj;</span>;
Skeleton.propTypes = {
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
export default Skeleton;

View File

@ -0,0 +1,12 @@
import React from 'react';
interface Props {
width?: number | string;
height?: number | string;
}
export const Skeleton: React.FC<Props> = ({ width, height }) => (
<span className='skeleton' style={{ width, height }}>
&zwnj;
</span>
);

View File

@ -1,18 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
const TimelineHint = ({ resource, url }) => (
<div className='timeline-hint'>
<strong><FormattedMessage id='timeline_hint.remote_resource_not_displayed' defaultMessage='{resource} from other servers are not displayed.' values={{ resource }} /></strong>
<br />
<a href={url} target='_blank' rel='noopener'><FormattedMessage id='account.browse_more_on_origin_server' defaultMessage='Browse more on the original profile' /></a>
</div>
);
TimelineHint.propTypes = {
resource: PropTypes.node.isRequired,
url: PropTypes.string.isRequired,
};
export default TimelineHint;

View File

@ -0,0 +1,27 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
interface Props {
resource: JSX.Element;
url: string;
}
export const TimelineHint: React.FC<Props> = ({ resource, url }) => (
<div className='timeline-hint'>
<strong>
<FormattedMessage
id='timeline_hint.remote_resource_not_displayed'
defaultMessage='{resource} from other servers are not displayed.'
values={{ resource }}
/>
</strong>
<br />
<a href={url} target='_blank' rel='noopener noreferrer'>
<FormattedMessage
id='account.browse_more_on_origin_server'
defaultMessage='Browse more on the original profile'
/>
</a>
</div>
);

View File

@ -8,7 +8,7 @@ import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
import Account from 'mastodon/containers/account_container'; import Account from 'mastodon/containers/account_container';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import { ServerHeroImage } from 'mastodon/components/server_hero_image'; import { ServerHeroImage } from 'mastodon/components/server_hero_image';

View File

@ -12,7 +12,7 @@ import ColumnBackButton from '../../components/column_back_button';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import TimelineHint from 'mastodon/components/timeline_hint'; import { TimelineHint } from 'mastodon/components/timeline_hint';
import { me } from 'mastodon/initial_state'; import { me } from 'mastodon/initial_state';
import LimitedAccountHint from './components/limited_account_hint'; import LimitedAccountHint from './components/limited_account_hint';
import { getAccountHidden } from 'mastodon/selectors'; import { getAccountHidden } from 'mastodon/selectors';

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Blurhash } from 'mastodon/components/blurhash'; import { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag'; import { accountsCountRenderer } from 'mastodon/components/hashtag';
import ShortNumber from 'mastodon/components/short_number'; import ShortNumber from 'mastodon/components/short_number';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
import classNames from 'classnames'; import classNames from 'classnames';
export default class Story extends React.PureComponent { export default class Story extends React.PureComponent {

View File

@ -17,7 +17,7 @@ import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container'; import HeaderContainer from '../account_timeline/containers/header_container';
import ColumnBackButton from '../../components/column_back_button'; import ColumnBackButton from '../../components/column_back_button';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import TimelineHint from 'mastodon/components/timeline_hint'; import { TimelineHint } from 'mastodon/components/timeline_hint';
import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
import { getAccountHidden } from 'mastodon/selectors'; import { getAccountHidden } from 'mastodon/selectors';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; import { normalizeForLookup } from 'mastodon/reducers/accounts_map';

View File

@ -17,7 +17,7 @@ import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container'; import HeaderContainer from '../account_timeline/containers/header_container';
import ColumnBackButton from '../../components/column_back_button'; import ColumnBackButton from '../../components/column_back_button';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import TimelineHint from 'mastodon/components/timeline_hint'; import { TimelineHint } from 'mastodon/components/timeline_hint';
import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
import { getAccountHidden } from 'mastodon/selectors'; import { getAccountHidden } from 'mastodon/selectors';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; import { normalizeForLookup } from 'mastodon/reducers/accounts_map';

View File

@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet';
import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import api from 'mastodon/api'; import api from 'mastodon/api';
import Skeleton from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },

View File

@ -85,7 +85,7 @@ class EmbedModal extends ImmutablePureComponent {
className='embed-modal__iframe' className='embed-modal__iframe'
frameBorder='0' frameBorder='0'
ref={this.setIframeRef} ref={this.setIframeRef}
sandbox='allow-same-origin' sandbox='allow-scripts allow-same-origin'
title='preview' title='preview'
/> />
</div> </div>

View File

@ -14,9 +14,9 @@ class Admin::Metrics::Dimension
}.freeze }.freeze
def self.retrieve(dimension_keys, start_at, end_at, limit, params) def self.retrieve(dimension_keys, start_at, end_at, limit, params)
Array(dimension_keys).map do |key| Array(dimension_keys).filter_map do |key|
klass = DIMENSIONS[key.to_sym] klass = DIMENSIONS[key.to_sym]
klass&.new(start_at, end_at, limit, klass.with_params? ? params.require(key.to_sym) : nil) klass&.new(start_at, end_at, limit, klass.with_params? ? params.require(key.to_sym) : nil)
end.compact end
end end
end end

View File

@ -19,9 +19,9 @@ class Admin::Metrics::Measure
}.freeze }.freeze
def self.retrieve(measure_keys, start_at, end_at, params) def self.retrieve(measure_keys, start_at, end_at, params)
Array(measure_keys).map do |key| Array(measure_keys).filter_map do |key|
klass = MEASURES[key.to_sym] klass = MEASURES[key.to_sym]
klass&.new(start_at, end_at, klass.with_params? ? params.require(key.to_sym) : nil) klass&.new(start_at, end_at, klass.with_params? ? params.require(key.to_sym) : nil)
end.compact end
end end
end end

View File

@ -64,7 +64,7 @@ module Extractor
end_position = match_data.char_end(1) end_position = match_data.char_end(1)
after = ::Regexp.last_match.post_match after = ::Regexp.last_match.post_match
if %r{\A://}.match?(after) if after.start_with?('://')
hash_text.match(/(.+)(https?\Z)/) do |matched| hash_text.match(/(.+)(https?\Z)/) do |matched|
hash_text = matched[1] hash_text = matched[1]
end_position -= matched[2].codepoint_length end_position -= matched[2].codepoint_length

View File

@ -213,7 +213,7 @@ class FeedManager
timeline_key = key(:home, account.id) timeline_key = key(:home, account.id)
timeline_status_ids = redis.zrange(timeline_key, 0, -1) timeline_status_ids = redis.zrange(timeline_key, 0, -1)
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) reblogged_ids = Status.where(id: statuses.filter_map(&:reblog_of_id), account: target_account).pluck(:id)
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id)
target_statuses = statuses.select do |status| target_statuses = statuses.select do |status|
@ -233,7 +233,7 @@ class FeedManager
timeline_key = key(:list, list.id) timeline_key = key(:list, list.id)
timeline_status_ids = redis.zrange(timeline_key, 0, -1) timeline_status_ids = redis.zrange(timeline_key, 0, -1)
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) reblogged_ids = Status.where(id: statuses.filter_map(&:reblog_of_id), account: target_account).pluck(:id)
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id)
target_statuses = statuses.select do |status| target_statuses = statuses.select do |status|
@ -603,9 +603,9 @@ class FeedManager
arr arr
end end
crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).index_with(true) crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map(&:in_reply_to_account_id)).pluck(:target_account_id).index_with(true)
crutches[:languages] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id)).pluck(:target_account_id, :languages).to_h crutches[:languages] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id)).pluck(:target_account_id, :languages).to_h
crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).index_with(true) crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map { |s| s.account_id if s.reblog? }, show_reblogs: false).pluck(:target_account_id).index_with(true)
crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true) crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true) crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.flat_map { |s| [s.account.domain, s.reblog&.account&.domain] }.compact).pluck(:domain).index_with(true) crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.flat_map { |s| [s.account.domain, s.reblog&.account&.domain] }.compact).pluck(:domain).index_with(true)

View File

@ -299,11 +299,11 @@ class Account < ApplicationRecord
end end
def fields def fields
(self[:fields] || []).map do |f| (self[:fields] || []).filter_map do |f|
Account::Field.new(self, f) Account::Field.new(self, f)
rescue rescue
nil nil
end.compact end
end end
def fields_attributes=(attributes) def fields_attributes=(attributes)

View File

@ -117,12 +117,12 @@ class AccountStatusesCleanupPolicy < ApplicationRecord
private private
def update_last_inspected def update_last_inspected
if EXCEPTION_BOOLS.map { |name| attribute_change_to_be_saved(name) }.compact.include?([true, false]) if EXCEPTION_BOOLS.filter_map { |name| attribute_change_to_be_saved(name) }.include?([true, false])
# Policy has been widened in such a way that any previously-inspected status # Policy has been widened in such a way that any previously-inspected status
# may need to be deleted, so we'll have to start again. # may need to be deleted, so we'll have to start again.
redis.del("account_cleanup:#{account_id}") redis.del("account_cleanup:#{account_id}")
end end
redis.del("account_cleanup:#{account_id}") if EXCEPTION_THRESHOLDS.map { |name| attribute_change_to_be_saved(name) }.compact.any? { |old, new| old.present? && (new.nil? || new > old) } redis.del("account_cleanup:#{account_id}") if EXCEPTION_THRESHOLDS.filter_map { |name| attribute_change_to_be_saved(name) }.any? { |old, new| old.present? && (new.nil? || new > old) }
end end
def validate_local_account def validate_local_account

View File

@ -48,14 +48,14 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source
end end
def setting_to_usernames_and_domains def setting_to_usernames_and_domains
setting.split(',').map do |str| setting.split(',').filter_map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@', 2) username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
domain = nil if TagManager.instance.local_domain?(domain) domain = nil if TagManager.instance.local_domain?(domain)
next if username.blank? next if username.blank?
[username.downcase, domain&.downcase] [username.downcase, domain&.downcase]
end.compact end
end end
def setting def setting

View File

@ -20,7 +20,7 @@ class AccountSuggestions::Source
map = scope.index_by { |account| to_ordered_list_key(account) } map = scope.index_by { |account| to_ordered_list_key(account) }
ordered_list.map { |ordered_list_key| map[ordered_list_key] }.compact.map do |account| ordered_list.filter_map { |ordered_list_key| map[ordered_list_key] }.map do |account|
AccountSuggestions::Suggestion.new( AccountSuggestions::Suggestion.new(
account: account, account: account,
source: key source: key

View File

@ -22,7 +22,7 @@ class FollowRecommendationFilter
account_ids = redis.zrevrange("follow_recommendations:#{@language}", 0, -1).map(&:to_i) account_ids = redis.zrevrange("follow_recommendations:#{@language}", 0, -1).map(&:to_i)
accounts = Account.where(id: account_ids).index_by(&:id) accounts = Account.where(id: account_ids).index_by(&:id)
account_ids.map { |id| accounts[id] }.compact account_ids.filter_map { |id| accounts[id] }
end end
end end
end end

View File

@ -114,7 +114,7 @@ class Notification < ApplicationRecord
ActiveRecord::Associations::Preloader.new.preload(grouped_notifications, associations) ActiveRecord::Associations::Preloader.new.preload(grouped_notifications, associations)
end end
unique_target_statuses = notifications.map(&:target_status).compact.uniq unique_target_statuses = notifications.filter_map(&:target_status).uniq
# Call cache_collection in block # Call cache_collection in block
cached_statuses_by_id = yield(unique_target_statuses).index_by(&:id) cached_statuses_by_id = yield(unique_target_statuses).index_by(&:id)

View File

@ -125,7 +125,7 @@ class UserRole < ApplicationRecord
end end
def permissions_as_keys=(value) def permissions_as_keys=(value)
self.permissions = value.map(&:presence).compact.reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask } self.permissions = value.filter_map(&:presence).reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask }
end end
def can?(*any_of_privileges) def can?(*any_of_privileges)

View File

@ -53,7 +53,7 @@ class Webhook < ApplicationRecord
end end
def strip_events def strip_events
self.events = events.map { |str| str.strip.presence }.compact if events.present? self.events = events.filter_map { |str| str.strip.presence } if events.present?
end end
def generate_secret def generate_secret

View File

@ -68,7 +68,7 @@ class ProcessMentionsService < BaseService
def assign_mentions! def assign_mentions!
# Make sure we never mention blocked accounts # Make sure we never mention blocked accounts
unless @current_mentions.empty? unless @current_mentions.empty?
mentioned_domains = @current_mentions.map { |m| m.account.domain }.compact.uniq mentioned_domains = @current_mentions.filter_map { |m| m.account.domain }.uniq
blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains)) blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains))
mentioned_account_ids = @current_mentions.map(&:account_id) mentioned_account_ids = @current_mentions.map(&:account_id)
blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id))

View File

@ -4,14 +4,14 @@ class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
return if value.blank? return if value.blank?
usernames_and_domains = value.split(',').map do |str| usernames_and_domains = value.split(',').filter_map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@', 2) username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
domain = nil if TagManager.instance.local_domain?(domain) domain = nil if TagManager.instance.local_domain?(domain)
next if username.blank? next if username.blank?
[str, username, domain] [str, username, domain]
end.compact end
usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)| usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)|
str unless Account.find_remote(username, domain) str unless Account.find_remote(username, domain)

View File

@ -5,6 +5,7 @@
= render 'auth/shared/progress', stage: 'confirm' = render 'auth/shared/progress', stage: 'confirm'
= hidden_field_tag :confirmation_token, params[:confirmation_token] = hidden_field_tag :confirmation_token, params[:confirmation_token]
= hidden_field_tag :redirect_to_app, params[:redirect_to_app]
%p.lead= t('auth.captcha_confirmation.hint_html') %p.lead= t('auth.captcha_confirmation.hint_html')

View File

@ -5,9 +5,9 @@ class MigrateUnavailableInboxes < ActiveRecord::Migration[5.2]
redis = RedisConfiguration.pool.checkout redis = RedisConfiguration.pool.checkout
urls = redis.smembers('unavailable_inboxes') urls = redis.smembers('unavailable_inboxes')
hosts = urls.map do |url| hosts = urls.filter_map do |url|
Addressable::URI.parse(url).normalized_host Addressable::URI.parse(url).normalized_host
end.compact.uniq end.uniq
UnavailableDomain.delete_all UnavailableDomain.delete_all

View File

@ -67,7 +67,7 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"glob": "^10.2.2", "glob": "^10.2.6",
"history": "^4.10.1", "history": "^4.10.1",
"http-link-header": "^1.1.1", "http-link-header": "^1.1.1",
"immutable": "^4.3.0", "immutable": "^4.3.0",
@ -116,7 +116,7 @@
"regenerator-runtime": "^0.13.11", "regenerator-runtime": "^0.13.11",
"requestidlecallback": "^0.3.0", "requestidlecallback": "^0.3.0",
"reselect": "^4.1.8", "reselect": "^4.1.8",
"rimraf": "^5.0.0", "rimraf": "^5.0.1",
"sass": "^1.62.1", "sass": "^1.62.1",
"sass-loader": "^10.2.0", "sass-loader": "^10.2.0",
"stacktrace-js": "^2.0.2", "stacktrace-js": "^2.0.2",
@ -131,7 +131,7 @@
"webpack-assets-manifest": "^4.0.6", "webpack-assets-manifest": "^4.0.6",
"webpack-bundle-analyzer": "^4.8.0", "webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-merge": "^5.8.0", "webpack-merge": "^5.9.0",
"wicg-inert": "^3.1.2", "wicg-inert": "^3.1.2",
"workbox-expiration": "^6.5.4", "workbox-expiration": "^6.5.4",
"workbox-precaching": "^6.5.4", "workbox-precaching": "^6.5.4",
@ -178,8 +178,8 @@
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"@types/webpack": "^4.41.33", "@types/webpack": "^4.41.33",
"@types/yargs": "^17.0.24", "@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^5.59.6", "@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.6", "@typescript-eslint/parser": "^5.59.7",
"babel-jest": "^29.5.0", "babel-jest": "^29.5.0",
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@ -199,7 +199,7 @@
"prettier": "^2.8.8", "prettier": "^2.8.8",
"react-intl-translations-manager": "^5.0.3", "react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"stylelint": "^15.6.1", "stylelint": "^15.6.2",
"stylelint-config-standard-scss": "^9.0.0", "stylelint-config-standard-scss": "^9.0.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"webpack-dev-server": "^3.11.3", "webpack-dev-server": "^3.11.3",
@ -216,7 +216,7 @@
}, },
"lint-staged": { "lint-staged": {
"*": "prettier --ignore-unknown --write", "*": "prettier --ignore-unknown --write",
"Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop -a", "Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a",
"*.{js,jsx,ts,tsx}": "eslint --fix", "*.{js,jsx,ts,tsx}": "eslint --fix",
"*.{css,scss}": "stylelint --fix" "*.{css,scss}": "stylelint --fix"
} }

View File

@ -18,4 +18,59 @@ describe Admin::AnnouncementsController do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
describe 'GET #new' do
it 'returns http success and renders new' do
get :new
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
describe 'GET #edit' do
let(:announcement) { Fabricate(:announcement) }
it 'returns http success and renders edit' do
get :edit, params: { id: announcement.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:edit)
end
end
describe 'POST #create' do
it 'creates a new announcement and redirects' do
expect do
post :create, params: { announcement: { text: 'The announcement message.' } }
end.to change(Announcement, :count).by(1)
expect(response).to redirect_to(admin_announcements_path)
expect(flash.notice).to match(I18n.t('admin.announcements.published_msg'))
end
end
describe 'PUT #update' do
let(:announcement) { Fabricate(:announcement, text: 'Original text') }
it 'updates an announcement and redirects' do
put :update, params: { id: announcement.id, announcement: { text: 'Updated text.' } }
expect(response).to redirect_to(admin_announcements_path)
expect(flash.notice).to match(I18n.t('admin.announcements.updated_msg'))
end
end
describe 'DELETE #destroy' do
let!(:announcement) { Fabricate(:announcement, text: 'Original text') }
it 'destroys an announcement and redirects' do
expect do
delete :destroy, params: { id: announcement.id }
end.to change(Announcement, :count).by(-1)
expect(response).to redirect_to(admin_announcements_path)
expect(flash.notice).to match(I18n.t('admin.announcements.destroyed_msg'))
end
end
end end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::FeaturedTagsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

View File

@ -3,5 +3,5 @@
Fabricator(:featured_tag) do Fabricator(:featured_tag) do
account account
tag tag
name 'Tag' name { sequence(:name) { |i| "Tag#{i}" } }
end end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'email confirmation flow when captcha is enabled' do
let(:user) { Fabricate(:user, confirmed_at: nil, confirmation_token: 'foobar', created_by_application: client_app) }
let(:client_app) { nil }
before do
# rubocop:disable RSpec/AnyInstance -- easiest way to deal with that that I know of
allow_any_instance_of(Auth::ConfirmationsController).to receive(:captcha_enabled?).and_return(true)
allow_any_instance_of(Auth::ConfirmationsController).to receive(:check_captcha!).and_return(true)
allow_any_instance_of(Auth::ConfirmationsController).to receive(:render_captcha).and_return(nil)
# rubocop:enable RSpec/AnyInstance
end
context 'when the user signed up through an app' do
let(:client_app) { Fabricate(:application) }
it 'logs in' do
visit "/auth/confirmation?confirmation_token=#{user.confirmation_token}&redirect_to_app=true"
# It presents the user with a captcha form
expect(page).to have_title(I18n.t('auth.captcha_confirmation.title'))
# It does not confirm the user just yet
expect(user.reload.confirmed?).to be false
# It redirects to app and confirms user
click_on I18n.t('challenge.confirm')
expect(user.reload.confirmed?).to be true
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)
end
end
end

View File

@ -11,75 +11,79 @@ RSpec.describe StatusPolicy, type: :model do
let(:bob) { Fabricate(:account, username: 'bob') } let(:bob) { Fabricate(:account, username: 'bob') }
let(:status) { Fabricate(:status, account: alice) } let(:status) { Fabricate(:status, account: alice) }
permissions :show?, :reblog? do context 'with the permissions of show? and reblog?' do
it 'grants access when no viewer' do permissions :show?, :reblog? do
expect(subject).to permit(nil, status) it 'grants access when no viewer' do
end expect(subject).to permit(nil, status)
end
it 'denies access when viewer is blocked' do it 'denies access when viewer is blocked' do
block = Fabricate(:block) block = Fabricate(:block)
status.visibility = :private status.visibility = :private
status.account = block.target_account status.account = block.target_account
expect(subject).to_not permit(block.account, status) expect(subject).to_not permit(block.account, status)
end
end end
end end
permissions :show? do context 'with the permission of show?' do
it 'grants access when direct and account is viewer' do permissions :show? do
status.visibility = :direct it 'grants access when direct and account is viewer' do
status.visibility = :direct
expect(subject).to permit(status.account, status) expect(subject).to permit(status.account, status)
end end
it 'grants access when direct and viewer is mentioned' do it 'grants access when direct and viewer is mentioned' do
status.visibility = :direct status.visibility = :direct
status.mentions = [Fabricate(:mention, account: alice)] status.mentions = [Fabricate(:mention, account: alice)]
expect(subject).to permit(alice, status) expect(subject).to permit(alice, status)
end end
it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do
status.visibility = :direct status.visibility = :direct
status.mentions = [Fabricate(:mention, account: bob)] status.mentions = [Fabricate(:mention, account: bob)]
status.mentions.load status.mentions.load
expect(subject).to permit(bob, status) expect(subject).to permit(bob, status)
end end
it 'denies access when direct and viewer is not mentioned' do it 'denies access when direct and viewer is not mentioned' do
viewer = Fabricate(:account) viewer = Fabricate(:account)
status.visibility = :direct status.visibility = :direct
expect(subject).to_not permit(viewer, status) expect(subject).to_not permit(viewer, status)
end end
it 'grants access when private and account is viewer' do it 'grants access when private and account is viewer' do
status.visibility = :private status.visibility = :private
expect(subject).to permit(status.account, status) expect(subject).to permit(status.account, status)
end end
it 'grants access when private and account is following viewer' do it 'grants access when private and account is following viewer' do
follow = Fabricate(:follow) follow = Fabricate(:follow)
status.visibility = :private status.visibility = :private
status.account = follow.target_account status.account = follow.target_account
expect(subject).to permit(follow.account, status) expect(subject).to permit(follow.account, status)
end end
it 'grants access when private and viewer is mentioned' do it 'grants access when private and viewer is mentioned' do
status.visibility = :private status.visibility = :private
status.mentions = [Fabricate(:mention, account: alice)] status.mentions = [Fabricate(:mention, account: alice)]
expect(subject).to permit(alice, status) expect(subject).to permit(alice, status)
end end
it 'denies access when private and viewer is not mentioned or followed' do it 'denies access when private and viewer is not mentioned or followed' do
viewer = Fabricate(:account) viewer = Fabricate(:account)
status.visibility = :private status.visibility = :private
expect(subject).to_not permit(viewer, status) expect(subject).to_not permit(viewer, status)
end
end end
it 'denies access when local-only and the viewer is not logged in' do it 'denies access when local-only and the viewer is not logged in' do
@ -95,55 +99,63 @@ RSpec.describe StatusPolicy, type: :model do
end end
end end
permissions :reblog? do context 'with the permission of reblog?' do
it 'denies access when private' do permissions :reblog? do
viewer = Fabricate(:account) it 'denies access when private' do
status.visibility = :private viewer = Fabricate(:account)
status.visibility = :private
expect(subject).to_not permit(viewer, status) expect(subject).to_not permit(viewer, status)
end end
it 'denies access when direct' do it 'denies access when direct' do
viewer = Fabricate(:account) viewer = Fabricate(:account)
status.visibility = :direct status.visibility = :direct
expect(subject).to_not permit(viewer, status) expect(subject).to_not permit(viewer, status)
end
end end
end end
permissions :destroy?, :unreblog? do context 'with the permissions of destroy? and unreblog?' do
it 'grants access when account is deleter' do permissions :destroy?, :unreblog? do
expect(subject).to permit(status.account, status) it 'grants access when account is deleter' do
end expect(subject).to permit(status.account, status)
end
it 'denies access when account is not deleter' do it 'denies access when account is not deleter' do
expect(subject).to_not permit(bob, status) expect(subject).to_not permit(bob, status)
end end
it 'denies access when no deleter' do it 'denies access when no deleter' do
expect(subject).to_not permit(nil, status) expect(subject).to_not permit(nil, status)
end
end end
end end
permissions :favourite? do context 'with the permission of favourite?' do
it 'grants access when viewer is not blocked' do permissions :favourite? do
follow = Fabricate(:follow) it 'grants access when viewer is not blocked' do
status.account = follow.target_account follow = Fabricate(:follow)
status.account = follow.target_account
expect(subject).to permit(follow.account, status) expect(subject).to permit(follow.account, status)
end end
it 'denies when viewer is blocked' do it 'denies when viewer is blocked' do
block = Fabricate(:block) block = Fabricate(:block)
status.account = block.target_account status.account = block.target_account
expect(subject).to_not permit(block.account, status) expect(subject).to_not permit(block.account, status)
end
end end
end end
permissions :update? do context 'with the permission of update?' do
it 'grants access if owner' do permissions :update? do
expect(subject).to permit(status.account, status) it 'grants access if owner' do
expect(subject).to permit(status.account, status)
end
end end
end end
end end

View File

@ -15,7 +15,7 @@ RSpec.describe StatusRelationshipsPresenter do
let(:presenter) { StatusRelationshipsPresenter.new(statuses, current_account_id, **options) } let(:presenter) { StatusRelationshipsPresenter.new(statuses, current_account_id, **options) }
let(:current_account_id) { Fabricate(:account).id } let(:current_account_id) { Fabricate(:account).id }
let(:statuses) { [Fabricate(:status)] } let(:statuses) { [Fabricate(:status)] }
let(:status_ids) { statuses.map(&:id) + statuses.map(&:reblog_of_id).compact } let(:status_ids) { statuses.map(&:id) + statuses.filter_map(&:reblog_of_id) }
let(:default_map) { { 1 => true } } let(:default_map) { { 1 => true } }
context 'when options are not set' do context 'when options are not set' do

View File

@ -0,0 +1,201 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'FeaturedTags' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET /api/v1/featured_tags' do
context 'with wrong scope' do
before do
get '/api/v1/featured_tags', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
get '/api/v1/featured_tags'
expect(response).to have_http_status(401)
end
end
it 'returns http success' do
get '/api/v1/featured_tags', headers: headers
expect(response).to have_http_status(200)
end
context 'when the requesting user has no featured tag' do
before { Fabricate.times(3, :featured_tag) }
it 'returns an empty body' do
get '/api/v1/featured_tags', headers: headers
body = body_as_json
expect(body).to be_empty
end
end
context 'when the requesting user has featured tags' do
let!(:user_featured_tags) { Fabricate.times(5, :featured_tag, account: user.account) }
it 'returns only the featured tags belonging to the requesting user' do
get '/api/v1/featured_tags', headers: headers
body = body_as_json
expected_ids = user_featured_tags.pluck(:id).map(&:to_s)
expect(body.pluck(:id)).to match_array(expected_ids)
end
end
end
describe 'POST /api/v1/featured_tags' do
let(:params) { { name: 'tag' } }
it 'returns http success' 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
body = body_as_json
expect(body[:name]).to eq(params[:name])
end
it 'creates a new featured tag for the requesting user' do
post '/api/v1/featured_tags', headers: headers, params: params
featured_tag = FeaturedTag.find_by(name: params[:name], account: user.account)
expect(featured_tag).to be_present
end
context 'with wrong scope' do
before do
post '/api/v1/featured_tags', headers: headers, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
post '/api/v1/featured_tags', params: params
expect(response).to have_http_status(401)
end
end
context 'when required param "name" is not provided' do
it 'returns http bad request' do
post '/api/v1/featured_tags', headers: headers
expect(response).to have_http_status(400)
end
end
context 'when provided tag name is invalid' do
let(:params) { { name: 'asj&*!' } }
it 'returns http unprocessable entity' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(422)
end
end
context 'when tag name is already taken' do
before do
FeaturedTag.create(name: params[:name], account: user.account)
end
it 'returns http unprocessable entity' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/featured_tags' do
let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) }
let(:id) { featured_tag.id }
it 'returns http success' 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
body = body_as_json
expect(body).to be_empty
end
it 'deletes the featured tag' do
delete "/api/v1/featured_tags/#{id}", headers: headers
featured_tag = FeaturedTag.find_by(id: id)
expect(featured_tag).to be_nil
end
context 'with wrong scope' do
before do
delete "/api/v1/featured_tags/#{id}", headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
delete "/api/v1/featured_tags/#{id}"
expect(response).to have_http_status(401)
end
end
context 'when featured tag with given id does not exist' do
it 'returns http not found' do
delete '/api/v1/featured_tags/0', headers: headers
expect(response).to have_http_status(404)
end
end
context 'when deleting a featured tag of another user' do
let!(:other_user_featured_tag) { Fabricate(:featured_tag) }
let(:id) { other_user_featured_tag.id }
it 'returns http not found' do
delete "/api/v1/featured_tags/#{id}", headers: headers
expect(response).to have_http_status(404)
end
end
end
end

147
yarn.lock
View File

@ -2449,15 +2449,15 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.59.6": "@typescript-eslint/eslint-plugin@^5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz#a350faef1baa1e961698240f922d8de1761a9e2b" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz#e470af414f05ecfdc05a23e9ce6ec8f91db56fe2"
integrity sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== integrity sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==
dependencies: dependencies:
"@eslint-community/regexpp" "^4.4.0" "@eslint-community/regexpp" "^4.4.0"
"@typescript-eslint/scope-manager" "5.59.6" "@typescript-eslint/scope-manager" "5.59.7"
"@typescript-eslint/type-utils" "5.59.6" "@typescript-eslint/type-utils" "5.59.7"
"@typescript-eslint/utils" "5.59.6" "@typescript-eslint/utils" "5.59.7"
debug "^4.3.4" debug "^4.3.4"
grapheme-splitter "^1.0.4" grapheme-splitter "^1.0.4"
ignore "^5.2.0" ignore "^5.2.0"
@ -2465,31 +2465,31 @@
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/parser@^5.59.6": "@typescript-eslint/parser@^5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.6.tgz#bd36f71f5a529f828e20b627078d3ed6738dbb40" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.7.tgz#02682554d7c1028b89aa44a48bf598db33048caa"
integrity sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== integrity sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "5.59.6" "@typescript-eslint/scope-manager" "5.59.7"
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.7"
"@typescript-eslint/typescript-estree" "5.59.6" "@typescript-eslint/typescript-estree" "5.59.7"
debug "^4.3.4" debug "^4.3.4"
"@typescript-eslint/scope-manager@5.59.6": "@typescript-eslint/scope-manager@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz#0243f41f9066f3339d2f06d7f72d6c16a16769e2"
integrity sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== integrity sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==
dependencies: dependencies:
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.7"
"@typescript-eslint/visitor-keys" "5.59.6" "@typescript-eslint/visitor-keys" "5.59.7"
"@typescript-eslint/type-utils@5.59.6": "@typescript-eslint/type-utils@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz#89c97291371b59eb18a68039857c829776f1426d"
integrity sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== integrity sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==
dependencies: dependencies:
"@typescript-eslint/typescript-estree" "5.59.6" "@typescript-eslint/typescript-estree" "5.59.7"
"@typescript-eslint/utils" "5.59.6" "@typescript-eslint/utils" "5.59.7"
debug "^4.3.4" debug "^4.3.4"
tsutils "^3.21.0" tsutils "^3.21.0"
@ -2498,10 +2498,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32"
integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==
"@typescript-eslint/types@5.59.6": "@typescript-eslint/types@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.7.tgz#6f4857203fceee91d0034ccc30512d2939000742"
integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== integrity sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==
"@typescript-eslint/typescript-estree@5.59.0": "@typescript-eslint/typescript-estree@5.59.0":
version "5.59.0" version "5.59.0"
@ -2516,30 +2516,30 @@
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.59.6": "@typescript-eslint/typescript-estree@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz#b887acbd4b58e654829c94860dbff4ac55c5cff8"
integrity sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== integrity sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==
dependencies: dependencies:
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.7"
"@typescript-eslint/visitor-keys" "5.59.6" "@typescript-eslint/visitor-keys" "5.59.7"
debug "^4.3.4" debug "^4.3.4"
globby "^11.1.0" globby "^11.1.0"
is-glob "^4.0.3" is-glob "^4.0.3"
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/utils@5.59.6": "@typescript-eslint/utils@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.7.tgz#7adf068b136deae54abd9a66ba5a8780d2d0f898"
integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== integrity sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@types/json-schema" "^7.0.9" "@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12" "@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.59.6" "@typescript-eslint/scope-manager" "5.59.7"
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.7"
"@typescript-eslint/typescript-estree" "5.59.6" "@typescript-eslint/typescript-estree" "5.59.7"
eslint-scope "^5.1.1" eslint-scope "^5.1.1"
semver "^7.3.7" semver "^7.3.7"
@ -2551,12 +2551,12 @@
"@typescript-eslint/types" "5.59.0" "@typescript-eslint/types" "5.59.0"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.59.6": "@typescript-eslint/visitor-keys@5.59.7":
version "5.59.6" version "5.59.7"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz#09c36eaf268086b4fbb5eb9dc5199391b6485fc5"
integrity sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== integrity sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==
dependencies: dependencies:
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.7"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@webassemblyjs/ast@1.9.0": "@webassemblyjs/ast@1.9.0":
@ -5891,15 +5891,15 @@ glob-parent@^6.0.2:
dependencies: dependencies:
is-glob "^4.0.3" is-glob "^4.0.3"
glob@^10.0.0, glob@^10.2.2: glob@^10.2.5, glob@^10.2.6:
version "10.2.2" version "10.2.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.2.tgz#ce2468727de7e035e8ecf684669dc74d0526ab75" resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.6.tgz#1e27edbb3bbac055cb97113e27a066c100a4e5e1"
integrity sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ== integrity sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==
dependencies: dependencies:
foreground-child "^3.1.0" foreground-child "^3.1.0"
jackspeak "^2.0.3" jackspeak "^2.0.3"
minimatch "^9.0.0" minimatch "^9.0.1"
minipass "^5.0.0" minipass "^5.0.0 || ^6.0.2"
path-scurry "^1.7.0" path-scurry "^1.7.0"
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
@ -8135,10 +8135,10 @@ minimatch@^5.0.1:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimatch@^9.0.0: minimatch@^9.0.1:
version "9.0.0" version "9.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w== integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
@ -8189,6 +8189,11 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
"minipass@^5.0.0 || ^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81"
integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==
minizlib@^2.1.1: minizlib@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@ -10162,12 +10167,12 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@^5.0.0: rimraf@^5.0.1:
version "5.0.0" version "5.0.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.0.tgz#5bda14e410d7e4dd522154891395802ce032c2cb" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0"
integrity sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g== integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==
dependencies: dependencies:
glob "^10.0.0" glob "^10.2.5"
ripemd160@^2.0.0, ripemd160@^2.0.1: ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2" version "2.0.2"
@ -11045,10 +11050,10 @@ stylelint-scss@^4.6.0:
postcss-selector-parser "^6.0.11" postcss-selector-parser "^6.0.11"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
stylelint@^15.6.1: stylelint@^15.6.2:
version "15.6.1" version "15.6.2"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.6.1.tgz#e4cd33a3af88587b99a5d1328aedd8c298b6dc81" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.6.2.tgz#06d9005b62a83b72887eed623520e9b472af8c15"
integrity sha512-d8icFBlVl93Elf3Z5ABQNOCe4nx69is3D/NZhDLAie1eyYnpxfeKe7pCfqzT5W4F8vxHCLSDfV8nKNJzogvV2Q== integrity sha512-fjQWwcdUye4DU+0oIxNGwawIPC5DvG5kdObY5Sg4rc87untze3gC/5g/ikePqVjrAsBUZjwMN+pZsAYbDO6ArQ==
dependencies: dependencies:
"@csstools/css-parser-algorithms" "^2.1.1" "@csstools/css-parser-algorithms" "^2.1.1"
"@csstools/css-tokenizer" "^2.1.1" "@csstools/css-tokenizer" "^2.1.1"
@ -11968,10 +11973,10 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0" ansi-colors "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
webpack-merge@^5.8.0: webpack-merge@^5.9.0:
version "5.8.0" version "5.9.0"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
dependencies: dependencies:
clone-deep "^4.0.1" clone-deep "^4.0.1"
wildcard "^2.0.0" wildcard "^2.0.0"