mirror of
https://github.com/glitch-soc/mastodon.git
synced 2024-11-30 03:50:13 -05:00
Merge commit 'b2c5b20ef27edd948eca8d6bd2014b7a5efaec11' into glitch-soc/merge-upstream
This commit is contained in:
commit
a111fd7a0b
@ -43,23 +43,6 @@ Metrics/CyclomaticComplexity:
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 27
|
||||
|
||||
RSpec/AnyInstance:
|
||||
Exclude:
|
||||
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
||||
- 'spec/controllers/admin/accounts_controller_spec.rb'
|
||||
- 'spec/controllers/admin/resets_controller_spec.rb'
|
||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
||||
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
||||
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
|
||||
- 'spec/lib/request_spec.rb'
|
||||
- 'spec/lib/status_filter_spec.rb'
|
||||
- 'spec/models/account_spec.rb'
|
||||
- 'spec/models/setting_spec.rb'
|
||||
- 'spec/services/activitypub/process_collection_service_spec.rb'
|
||||
- 'spec/validators/follow_limit_validator_spec.rb'
|
||||
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
||||
- 'spec/workers/web/push_notification_worker_spec.rb'
|
||||
|
||||
# Configuration parameters: CountAsOne.
|
||||
RSpec/ExampleLength:
|
||||
Max: 22
|
||||
|
@ -7,6 +7,7 @@ class Api::BaseController < ApplicationController
|
||||
include RateLimitHeaders
|
||||
include AccessTokenTrackingConcern
|
||||
include ApiCachingConcern
|
||||
include Api::ContentSecurityPolicy
|
||||
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
@ -17,26 +18,6 @@ class Api::BaseController < ApplicationController
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
content_security_policy do |p|
|
||||
# Set every directive that does not have a fallback
|
||||
p.default_src :none
|
||||
p.frame_ancestors :none
|
||||
p.form_action :none
|
||||
|
||||
# Disable every directive with a fallback to cut on response size
|
||||
p.base_uri false
|
||||
p.font_src false
|
||||
p.img_src false
|
||||
p.style_src false
|
||||
p.media_src false
|
||||
p.frame_src false
|
||||
p.manifest_src false
|
||||
p.connect_src false
|
||||
p.script_src false
|
||||
p.child_src false
|
||||
p.worker_src false
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
|
||||
cache_if_unauthenticated!
|
||||
end
|
||||
|
||||
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
|
||||
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: show_rationale_in_response?
|
||||
end
|
||||
|
||||
private
|
||||
@ -25,4 +25,16 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
|
||||
def set_domain_blocks
|
||||
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
||||
end
|
||||
|
||||
def show_rationale_in_response?
|
||||
always_show_rationale? || show_rationale_for_user?
|
||||
end
|
||||
|
||||
def always_show_rationale?
|
||||
Setting.show_domain_blocks_rationale == 'all'
|
||||
end
|
||||
|
||||
def show_rationale_for_user?
|
||||
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
|
||||
end
|
||||
end
|
||||
|
16
app/controllers/api/v1/statuses/base_controller.rb
Normal file
16
app/controllers/api/v1/statuses/base_controller.rb
Normal file
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::BaseController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :set_status
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
@ -1,11 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::BookmarksController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
|
||||
before_action :require_user!
|
||||
before_action :set_status, only: [:create]
|
||||
skip_before_action :set_status, only: [:destroy]
|
||||
|
||||
def create
|
||||
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
|
||||
@ -28,13 +26,4 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@ -61,13 +58,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@ -1,11 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
|
||||
before_action :require_user!
|
||||
before_action :set_status, only: [:create]
|
||||
skip_before_action :set_status, only: [:destroy]
|
||||
|
||||
def create
|
||||
FavouriteService.new.call(current_account, @status)
|
||||
@ -30,13 +28,4 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||
before_action :set_status
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
@ -16,11 +13,4 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||
def status_edits
|
||||
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::MutesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
|
||||
before_action :require_user!
|
||||
before_action :set_status
|
||||
before_action :set_conversation
|
||||
|
||||
def create
|
||||
@ -24,13 +21,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation = @status.conversation
|
||||
raise Mastodon::ValidationError if @conversation.nil?
|
||||
|
@ -1,11 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||
before_action :require_user!
|
||||
before_action :set_status
|
||||
|
||||
def create
|
||||
StatusPin.create!(account: current_account, status: @status)
|
||||
@ -26,10 +23,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
end
|
||||
|
||||
def distribute_add_activity!
|
||||
json = ActiveModelSerializers::SerializableResource.new(
|
||||
@status,
|
||||
|
@ -1,10 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@ -57,13 +54,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@ -1,13 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
include Authorization
|
||||
class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
||||
before_action :require_user!
|
||||
before_action :set_reblog, only: [:create]
|
||||
skip_before_action :set_status
|
||||
|
||||
override_rate_limit_headers :create, family: :statuses
|
||||
|
||||
|
@ -1,21 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::SourcesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::SourcesController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||
before_action :set_status
|
||||
|
||||
def show
|
||||
render json: @status, serializer: REST::StatusSourceSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||
before_action :set_status
|
||||
before_action :set_translation
|
||||
|
||||
rescue_from TranslationService::NotConfiguredError, with: :not_found
|
||||
@ -24,13 +21,6 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def set_translation
|
||||
@translation = TranslateStatusService.new.call(@status, content_locale)
|
||||
end
|
||||
|
27
app/controllers/concerns/api/content_security_policy.rb
Normal file
27
app/controllers/concerns/api/content_security_policy.rb
Normal file
@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api::ContentSecurityPolicy
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
content_security_policy do |policy|
|
||||
# Set every directive that does not have a fallback
|
||||
policy.default_src :none
|
||||
policy.frame_ancestors :none
|
||||
policy.form_action :none
|
||||
|
||||
# Disable every directive with a fallback to cut on response size
|
||||
policy.base_uri false
|
||||
policy.font_src false
|
||||
policy.img_src false
|
||||
policy.style_src false
|
||||
policy.media_src false
|
||||
policy.frame_src false
|
||||
policy.manifest_src false
|
||||
policy.connect_src false
|
||||
policy.script_src false
|
||||
policy.child_src false
|
||||
policy.worker_src false
|
||||
end
|
||||
end
|
||||
end
|
@ -19,6 +19,8 @@ import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/s
|
||||
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
||||
import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
|
||||
|
||||
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
|
||||
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
@ -348,6 +350,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
let replyIcon;
|
||||
let replyIconComponent;
|
||||
let replyTitle;
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyIconComponent = ReplyIcon;
|
||||
@ -360,15 +363,20 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle = '';
|
||||
let reblogTitle, reblogIconComponent;
|
||||
|
||||
if (status.get('reblogged')) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
reblogIconComponent = RepeatIcon;
|
||||
} else if (reblogPrivate) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||
reblogIconComponent = RepeatPrivateIcon;
|
||||
} else {
|
||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
reblogIconComponent = RepeatDisabledIcon;
|
||||
}
|
||||
|
||||
const filterButton = this.props.onFilter && (
|
||||
@ -380,7 +388,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||
|
||||
|
@ -204,7 +204,7 @@ class ListTimeline extends PureComponent {
|
||||
</div>
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
||||
</label>
|
||||
|
@ -18,6 +18,8 @@ import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlin
|
||||
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
|
||||
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
||||
|
||||
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
|
||||
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
@ -280,6 +282,7 @@ class ActionBar extends PureComponent {
|
||||
|
||||
let replyIcon;
|
||||
let replyIconComponent;
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyIconComponent = ReplyIcon;
|
||||
@ -290,21 +293,26 @@ class ActionBar extends PureComponent {
|
||||
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle;
|
||||
let reblogTitle, reblogIconComponent;
|
||||
|
||||
if (status.get('reblogged')) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
reblogIconComponent = RepeatIcon;
|
||||
} else if (reblogPrivate) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||
reblogIconComponent = RepeatPrivateIcon;
|
||||
} else {
|
||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
reblogIconComponent = RepeatDisabledIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
"account.badges.group": "Groep",
|
||||
"account.block": "Blokkeer @{name}",
|
||||
"account.block_domain": "Blokkeer domein {domain}",
|
||||
"account.block_short": "Blokkeer",
|
||||
"account.blocked": "Geblokkeer",
|
||||
"account.browse_more_on_origin_server": "Verken die oorspronklike profiel",
|
||||
"account.cancel_follow_request": "Herroep volgversoek",
|
||||
@ -45,6 +46,7 @@
|
||||
"account.posts_with_replies": "Plasings en antwoorde",
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer",
|
||||
"account.requested_follow": "{name} het versoek om jou te volg",
|
||||
"account.share": "Deel @{name} se profiel",
|
||||
"account.show_reblogs": "Wys aangestuurde plasings van @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}",
|
||||
@ -82,6 +84,7 @@
|
||||
"column.community": "Plaaslike tydlyn",
|
||||
"column.directory": "Blaai deur profiele",
|
||||
"column.domain_blocks": "Geblokkeerde domeine",
|
||||
"column.favourites": "Gunstelinge",
|
||||
"column.follow_requests": "Volgversoeke",
|
||||
"column.home": "Tuis",
|
||||
"column.lists": "Lyste",
|
||||
@ -271,6 +274,7 @@
|
||||
"privacy.unlisted.short": "Ongelys",
|
||||
"privacy_policy.last_updated": "Laaste bywerking op {date}",
|
||||
"privacy_policy.title": "Privaatheidsbeleid",
|
||||
"regeneration_indicator.sublabel": "Jou tuis-voer word voorberei!",
|
||||
"reply_indicator.cancel": "Kanselleer",
|
||||
"report.placeholder": "Type or paste additional comments",
|
||||
"report.submit": "Submit report",
|
||||
|
@ -201,7 +201,7 @@
|
||||
"disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
|
||||
"dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
|
||||
"dismissable_banner.dismiss": "Адхіліць",
|
||||
"dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца прама зараз на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
|
||||
"dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца цяпер на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
|
||||
"dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.",
|
||||
"dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі",
|
||||
"dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.",
|
||||
@ -482,7 +482,7 @@
|
||||
"onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!",
|
||||
"onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}",
|
||||
"onboarding.share.next_steps": "Магчымыя наступныя крокі:",
|
||||
"onboarding.share.title": "Падзяліцеся сваім профілем",
|
||||
"onboarding.share.title": "Абагульце свой профіль",
|
||||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "Вы зрабілі гэта!",
|
||||
@ -493,7 +493,7 @@
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Share your profile",
|
||||
"onboarding.steps.share_profile.title": "Абагульць ваш профіль у Mastodon",
|
||||
"onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!",
|
||||
"onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Resultats de la cerca",
|
||||
"emoji_button.symbols": "Símbols",
|
||||
"emoji_button.travel": "Viatges i llocs",
|
||||
"empty_column.account_hides_collections": "Aquest usuari ha elegit no mostrar aquesta informació",
|
||||
"empty_column.account_suspended": "Compte suspès",
|
||||
"empty_column.account_timeline": "No hi ha tuts aquí!",
|
||||
"empty_column.account_unavailable": "Perfil no disponible",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Sykresultaten",
|
||||
"emoji_button.symbols": "Symboalen",
|
||||
"emoji_button.travel": "Reizgje en lokaasjes",
|
||||
"empty_column.account_hides_collections": "Dizze brûker hat derfoar keazen dizze ynformaasje net beskikber te meitsjen",
|
||||
"empty_column.account_suspended": "Account beskoattele",
|
||||
"empty_column.account_timeline": "Hjir binne gjin berjochten!",
|
||||
"empty_column.account_unavailable": "Profyl net beskikber",
|
||||
|
@ -41,6 +41,8 @@
|
||||
"account.languages": "Keisti prenumeruojamas kalbas",
|
||||
"account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.",
|
||||
"account.media": "Medija",
|
||||
"account.mention": "Paminėti @{name}",
|
||||
"account.moved_to": "{name} nurodė, kad dabar jų nauja paskyra yra:",
|
||||
"account.mute": "Užtildyti @{name}",
|
||||
"account.muted": "Užtildytas",
|
||||
"account.posts": "Toots",
|
||||
@ -53,10 +55,15 @@
|
||||
"account.unfollow": "Nebesekti",
|
||||
"account.unmute_short": "Atitildyti",
|
||||
"account_note.placeholder": "Click to add a note",
|
||||
"alert.unexpected.title": "Oi!",
|
||||
"alert.unexpected.message": "Įvyko netikėta klaida.",
|
||||
"alert.unexpected.title": "Ups!",
|
||||
"announcement.announcement": "Skelbimas",
|
||||
"attachments_list.unprocessed": "(neapdorotas)",
|
||||
"audio.hide": "Slėpti garsą",
|
||||
"autosuggest_hashtag.per_week": "{count} per savaitę",
|
||||
"boost_modal.combo": "Gali spausti {combo}, kad praleisti kitą kartą",
|
||||
"bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą",
|
||||
"bundle_column_error.error.body": "Užklausos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.",
|
||||
"bundle_column_error.error.title": "O, ne!",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.lists": "Sąrašai",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Rezultati iskanja",
|
||||
"emoji_button.symbols": "Simboli",
|
||||
"emoji_button.travel": "Potovanja in kraji",
|
||||
"empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo dal na voljo",
|
||||
"empty_column.account_suspended": "Račun je suspendiran",
|
||||
"empty_column.account_timeline": "Tukaj ni objav!",
|
||||
"empty_column.account_unavailable": "Profil ni na voljo",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Rezultati pretrage",
|
||||
"emoji_button.symbols": "Simboli",
|
||||
"emoji_button.travel": "Putovanja i mesta",
|
||||
"empty_column.account_hides_collections": "Ovaj korisnik je odlučio da ove informacije ne učini dostupnim",
|
||||
"empty_column.account_suspended": "Nalog je suspendovan",
|
||||
"empty_column.account_timeline": "Nema objava ovde!",
|
||||
"empty_column.account_unavailable": "Profil je nedostupan",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Резултати претраге",
|
||||
"emoji_button.symbols": "Симболи",
|
||||
"emoji_button.travel": "Путовања и места",
|
||||
"empty_column.account_hides_collections": "Овај корисник је одлучио да ове информације не учини доступним",
|
||||
"empty_column.account_suspended": "Налог је суспендован",
|
||||
"empty_column.account_timeline": "Нема објава овде!",
|
||||
"empty_column.account_unavailable": "Профил је недоступан",
|
||||
|
5
app/javascript/svg-icons/repeat_disabled.svg
Executable file
5
app/javascript/svg-icons/repeat_disabled.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 13V17.8787L17 15.8787V13H19Z"/>
|
||||
<path d="M2.41421 2.70711L1 4.12132L5 8.12132V11H7V10.1213L13.8787 17H6.85L8.4 15.45L7 14L3 18L7 22L8.4 20.55L6.85 19H15.8787L19.3848 22.5061L20.799 21.0919L2.41421 2.70711Z"/>
|
||||
<path d="M17.15 7H8.12132L6.12132 5H17.15L15.6 3.45L17 2L21 6L17 10L15.6 8.55L17.15 7Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 415 B |
5
app/javascript/svg-icons/repeat_private.svg
Executable file
5
app/javascript/svg-icons/repeat_private.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.4 15.45L7 14L3 18L7 22L8.4 20.55L6.85 19H13.5V18C13.5 17.6567 13.5638 17.3171 13.6988 17H6.85L8.4 15.45Z"/>
|
||||
<path d="M5 5V11H7V7H17.15L15.6 8.55L17 10L21 6L17 2L15.6 3.45L17.15 5H5Z"/>
|
||||
<path d="M16 22C15.7167 22 15.475 21.9083 15.275 21.725C15.0917 21.525 15 21.2833 15 21V18C15 17.7167 15.0917 17.4833 15.275 17.3C15.475 17.1 15.7167 17 16 17V16C16 15.45 16.1917 14.9833 16.575 14.6C16.975 14.2 17.45 14 18 14C18.55 14 19.0167 14.2 19.4 14.6C19.8 14.9833 20 15.45 20 16V17C20.2833 17 20.5167 17.1 20.7 17.3C20.9 17.4833 21 17.7167 21 18V21C21 21.2833 20.9 21.525 20.7 21.725C20.5167 21.9083 20.2833 22 20 22H16ZM17 17H19V16C19 15.7167 18.9 15.4833 18.7 15.3C18.5167 15.1 18.2833 15 18 15C17.7167 15 17.475 15.1 17.275 15.3C17.0917 15.4833 17 15.7167 17 16V17Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 879 B |
@ -33,11 +33,11 @@ class Webhook < ApplicationRecord
|
||||
validates :secret, presence: true, length: { minimum: 12 }
|
||||
validates :events, presence: true
|
||||
|
||||
validate :validate_events
|
||||
validate :events_validation_error, if: :invalid_events?
|
||||
validate :validate_permissions
|
||||
validate :validate_template
|
||||
|
||||
before_validation :strip_events
|
||||
normalizes :events, with: ->(events) { events.filter_map { |event| event.strip.presence } }
|
||||
before_validation :generate_secret
|
||||
|
||||
def rotate_secret!
|
||||
@ -69,8 +69,12 @@ class Webhook < ApplicationRecord
|
||||
|
||||
private
|
||||
|
||||
def validate_events
|
||||
errors.add(:events, :invalid) if events.any? { |e| EVENTS.exclude?(e) }
|
||||
def events_validation_error
|
||||
errors.add(:events, :invalid)
|
||||
end
|
||||
|
||||
def invalid_events?
|
||||
events.blank? || events.difference(EVENTS).any?
|
||||
end
|
||||
|
||||
def validate_permissions
|
||||
@ -88,10 +92,6 @@ class Webhook < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def strip_events
|
||||
self.events = events.filter_map { |str| str.strip.presence } if events.present?
|
||||
end
|
||||
|
||||
def generate_secret
|
||||
self.secret = SecureRandom.hex(20) if secret.blank?
|
||||
end
|
||||
|
@ -180,7 +180,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
end
|
||||
|
||||
def check_links!
|
||||
VerifyAccountLinksWorker.perform_async(@account.id)
|
||||
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), @account.id)
|
||||
end
|
||||
|
||||
def process_duplicate_accounts!
|
||||
|
@ -30,11 +30,7 @@ class UpdateAccountService < BaseService
|
||||
def check_links(account)
|
||||
return unless account.fields.any?(&:requires_verification?)
|
||||
|
||||
if account.local?
|
||||
VerifyAccountLinksWorker.perform_async(account.id)
|
||||
else
|
||||
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), account.id)
|
||||
end
|
||||
end
|
||||
|
||||
def process_hashtags(account)
|
||||
|
@ -53,3 +53,7 @@ af:
|
||||
position:
|
||||
elevated: kan nie hoër as jou huidige rol wees nie
|
||||
own_role: kan nie verander word met jou huidige rol nie
|
||||
webhook:
|
||||
attributes:
|
||||
events:
|
||||
invalid_permissions: geleenthede waartoe jy nie toegang het nie mag nie ingesluit word nie
|
||||
|
@ -5,7 +5,23 @@ af:
|
||||
contact_unavailable: NVT
|
||||
hosted_on: Mastodon gehuisves op %{domain}
|
||||
title: Aangaande
|
||||
accounts:
|
||||
follow: Volg
|
||||
followers:
|
||||
one: Volgeling
|
||||
other: Volgelinge
|
||||
following: Volg
|
||||
nothing_here: Daar is niks hier nie!
|
||||
posts:
|
||||
one: Plasing
|
||||
other: Plasings
|
||||
posts_tab_heading: Plasings
|
||||
admin:
|
||||
account_actions:
|
||||
action: Voer aksie uit
|
||||
title: Voer modereer aksie uit op %{acct}
|
||||
account_moderation_notes:
|
||||
create: Los nota
|
||||
accounts:
|
||||
location:
|
||||
local: Plaaslik
|
||||
@ -102,6 +118,7 @@ af:
|
||||
types:
|
||||
bookmarks: Boekmerke
|
||||
invites:
|
||||
invalid: Hierdie uitnodiging is nie geldig nie
|
||||
title: Nooi ander
|
||||
login_activities:
|
||||
description_html: Indien jy onbekende aktiwiteite gewaar, oorweeg dit om jou wagwoord te verander en tweefaktorverifikasie te aktiveer.
|
||||
|
@ -1418,6 +1418,7 @@ be:
|
||||
'86400': 1 дзень
|
||||
expires_in_prompt: Ніколі
|
||||
generate: Стварыць запрашальную спасылку
|
||||
invalid: Гэта запрашэнне несапраўднае
|
||||
invited_by: 'Вас запрасіў(-ла):'
|
||||
max_uses:
|
||||
few: "%{count} выкарыстанні"
|
||||
|
@ -1358,6 +1358,7 @@ ca:
|
||||
'86400': 1 dia
|
||||
expires_in_prompt: Mai
|
||||
generate: Genera
|
||||
invalid: Aquesta invitació no és vàlida
|
||||
invited_by: 'Has estat invitat per:'
|
||||
max_uses:
|
||||
one: 1 ús
|
||||
|
@ -1468,6 +1468,7 @@ cy:
|
||||
'86400': 1 diwrnod
|
||||
expires_in_prompt: Byth
|
||||
generate: Cynhyrchu dolen wahoddiad
|
||||
invalid: Nid yw'r gwahoddiad hwn yn ddilys
|
||||
invited_by: 'Cawsoch eich gwahodd gan:'
|
||||
max_uses:
|
||||
few: "%{count} defnydd"
|
||||
|
@ -1110,6 +1110,7 @@ da:
|
||||
functional: Din konto er fuldt funktionel.
|
||||
pending: Din ansøgning afventer gennemgang af vores medarbejdere. Dette kan tage noget tid. Du modtager en e-mail, hvis din ansøgning godkendes.
|
||||
redirecting_to: Din konto er inaktiv, da den pt. er omdirigerer til %{acct}.
|
||||
self_destruct: Da %{domain} er under nedlukning, vil kontoadgangen være begrænset.
|
||||
view_strikes: Se tidligere anmeldelser af din konto
|
||||
too_fast: Formularen indsendt for hurtigt, forsøg igen.
|
||||
use_security_key: Brug sikkerhedsnøgle
|
||||
@ -1367,6 +1368,7 @@ da:
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Aldrig
|
||||
generate: Generér invitationslink
|
||||
invalid: Denne invitation er ikke gyldig
|
||||
invited_by: 'Du blev inviteret af:'
|
||||
max_uses:
|
||||
one: 1 benyttelse
|
||||
@ -1579,6 +1581,9 @@ da:
|
||||
over_daily_limit: Den daglige grænse på %{limit} planlagte indlæg er nået
|
||||
over_total_limit: Grænsen på %{limit} planlagte indlæg er nået
|
||||
too_soon: Den planlagte dato skal være i fremtiden
|
||||
self_destruct:
|
||||
lead_html: Desværre lukker <strong>%{domain}</strong> permanent. Har man en konto dér, vil fortsat brug heraf ikke være mulig. Man kan dog stadig anmode om en sikkerhedskopi af sine data.
|
||||
title: Denne server er under nedlukning
|
||||
sessions:
|
||||
activity: Seneste aktivitet
|
||||
browser: Browser
|
||||
|
@ -1368,6 +1368,7 @@ de:
|
||||
'86400': 1 Tag
|
||||
expires_in_prompt: Nie
|
||||
generate: Einladungslink erstellen
|
||||
invalid: Diese Einladung ist ungültig
|
||||
invited_by: 'Du wurdest eingeladen von:'
|
||||
max_uses:
|
||||
one: Eine Verwendung
|
||||
|
@ -18,7 +18,7 @@ be:
|
||||
unconfirmed: Вы павінны пацвердзіць свой адрас электроннай пошты, перш чым працягнуць
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
action: Пацвердзіце адрас электроннай пошты
|
||||
action: Пацвердзіць адрас электроннай пошты
|
||||
action_with_app: Пацвердзіць і вярнуцца да %{app}
|
||||
explanation: Вы стварылі ўліковы запіс на %{host} з гэтым адрасам электроннай пошты. Вам спатрэбіцца ўсяго адзін клік, каб пацвердзіць яго. Калі гэта былі не вы, то проста праігнаруйце гэты ліст.
|
||||
explanation_when_pending: Вы падалі заяўку на запрашэнне на %{host} з гэтым адрасам электроннай пошты. Як толькі вы пацвердзіце свой адрас электроннай пошты, мы разгледзім вашу заяўку. Вы можаце ўвайсці, каб змяніць свае дадзеныя або выдаліць свой уліковы запіс, але вы не можаце атрымаць доступ да большасці функцый, пакуль ваш уліковы запіс не будзе зацверджаны. Калі ваша заяўка будзе адхілена, вашы даныя будуць выдалены, таму ад вас не спатрэбіцца ніякіх дадатковых дзеянняў. Калі гэта былі не вы, ігнаруйце гэты ліст
|
||||
|
@ -149,6 +149,7 @@ af:
|
||||
write:blocks: blokkeer rekeninge en domeine
|
||||
write:bookmarks: laat ’n boekmerk by plasings
|
||||
write:conversations: doof en wis gesprekke uit
|
||||
write:favourites: gunsteling plasings
|
||||
write:filters: skep filters
|
||||
write:follows: volg mense
|
||||
write:lists: skep lyste
|
||||
|
@ -1368,6 +1368,7 @@ es-AR:
|
||||
'86400': 1 día
|
||||
expires_in_prompt: Nunca
|
||||
generate: Generar enlace de invitación
|
||||
invalid: Esta invitación no es válida
|
||||
invited_by: 'Fuiste invitado por:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -1368,6 +1368,7 @@ es-MX:
|
||||
'86400': 1 día
|
||||
expires_in_prompt: Nunca
|
||||
generate: Generar
|
||||
invalid: Esta invitación no es válida
|
||||
invited_by: 'Fuiste invitado por:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -1368,6 +1368,7 @@ es:
|
||||
'86400': 1 día
|
||||
expires_in_prompt: Nunca
|
||||
generate: Generar
|
||||
invalid: Esta invitación no es válida
|
||||
invited_by: 'Fuiste invitado por:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -1363,6 +1363,7 @@ eu:
|
||||
'86400': Egun 1
|
||||
expires_in_prompt: Inoiz ez
|
||||
generate: Sortu
|
||||
invalid: Gonbidapen hau ez da baliozkoa
|
||||
invited_by: 'Honek gonbidatu zaitu:'
|
||||
max_uses:
|
||||
one: Erabilera 1
|
||||
|
@ -1157,6 +1157,7 @@ fa:
|
||||
'86400': ۱ روز
|
||||
expires_in_prompt: هیچ وقت
|
||||
generate: ساختن
|
||||
invalid: این دعوتنامه معتبر نیست
|
||||
invited_by: 'دعوتکنندهٔ شما:'
|
||||
max_uses:
|
||||
one: ۱ بار
|
||||
|
@ -1368,6 +1368,7 @@ fo:
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Ongantíð
|
||||
generate: Ger innbjóðingarleinki
|
||||
invalid: Henda innbjóðing er ikki gildug
|
||||
invited_by: 'Tú var bjóðað/ur av:'
|
||||
max_uses:
|
||||
one: 1 brúk
|
||||
|
@ -1368,6 +1368,7 @@ fr-QC:
|
||||
'86400': 1 jour
|
||||
expires_in_prompt: Jamais
|
||||
generate: Générer un lien d'invitation
|
||||
invalid: Cette invitation n’est pas valide
|
||||
invited_by: 'Vous avez été invité·e par :'
|
||||
max_uses:
|
||||
one: 1 utilisation
|
||||
|
@ -1368,6 +1368,7 @@ fr:
|
||||
'86400': 1 jour
|
||||
expires_in_prompt: Jamais
|
||||
generate: Générer un lien d'invitation
|
||||
invalid: Cette invitation n’est pas valide
|
||||
invited_by: 'Vous avez été invité·e par :'
|
||||
max_uses:
|
||||
one: 1 utilisation
|
||||
|
@ -1368,6 +1368,7 @@ fy:
|
||||
'86400': 1 dei
|
||||
expires_in_prompt: Nea
|
||||
generate: Utnûgingskeppeling generearje
|
||||
invalid: Dizze útnûging is net jildich
|
||||
invited_by: 'Jo binne útnûge troch:'
|
||||
max_uses:
|
||||
one: 1 kear
|
||||
|
@ -1368,6 +1368,7 @@ gl:
|
||||
'86400': 1 día
|
||||
expires_in_prompt: Nunca
|
||||
generate: Xerar
|
||||
invalid: Este convite non é válido
|
||||
invited_by: 'Convidoute:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -113,8 +113,8 @@ he:
|
||||
previous_strikes_description_html:
|
||||
many: לחשבון הזה יש <strong>%{count}</strong> פסילות.
|
||||
one: לחשבון הזה פסילה <strong>אחת</strong>.
|
||||
other: לחשבון הזה <strong>%{count}</strong> פסילות.
|
||||
two: לחשבון הזה <strong>%{count}</strong> פסילות.
|
||||
other: לחשבון הזה יש <strong>%{count}</strong> פסילות.
|
||||
two: לחשבון הזה יש <strong>שתי</strong> פסילות.
|
||||
promote: להעלות בדרגה
|
||||
protocol: פרטיכל
|
||||
public: פומבי
|
||||
@ -1418,6 +1418,7 @@ he:
|
||||
'86400': יום אחד
|
||||
expires_in_prompt: לעולם לא
|
||||
generate: יצירת קישור להזמנה
|
||||
invalid: הזמנה זו אינה תקפה
|
||||
invited_by: הוזמנת ע"י
|
||||
max_uses:
|
||||
many: "%{count} שימושים"
|
||||
|
@ -1368,6 +1368,7 @@ hu:
|
||||
'86400': 1 nap
|
||||
expires_in_prompt: Soha
|
||||
generate: Generálás
|
||||
invalid: Ez a meghívó nem érvényes
|
||||
invited_by: 'Téged meghívott:'
|
||||
max_uses:
|
||||
one: 1 használat
|
||||
|
@ -1372,6 +1372,7 @@ is:
|
||||
'86400': 1 dagur
|
||||
expires_in_prompt: Aldrei
|
||||
generate: Útbúa boðstengil
|
||||
invalid: Þetta boð er ekki gilt
|
||||
invited_by: 'Þér var boðið af:'
|
||||
max_uses:
|
||||
one: 1 afnot
|
||||
|
@ -1370,6 +1370,7 @@ it:
|
||||
'86400': 1 giorno
|
||||
expires_in_prompt: Mai
|
||||
generate: Genera
|
||||
invalid: Questo invito non è valido
|
||||
invited_by: 'Sei stato invitato da:'
|
||||
max_uses:
|
||||
one: un uso
|
||||
|
@ -1345,6 +1345,7 @@ ko:
|
||||
'86400': 1 일
|
||||
expires_in_prompt: 영원히
|
||||
generate: 초대 링크 생성하기
|
||||
invalid: 이 초대는 올바르지 않습니다
|
||||
invited_by: '당신을 초대한 사람:'
|
||||
max_uses:
|
||||
other: "%{count}회"
|
||||
|
@ -383,6 +383,7 @@ lt:
|
||||
'86400': 1 dienos
|
||||
expires_in_prompt: Niekada
|
||||
generate: Generuoti
|
||||
invalid: Šis kvietimas negalioja.
|
||||
invited_by: 'Jus pakvietė:'
|
||||
max_uses:
|
||||
few: "%{count} panaudojimai"
|
||||
|
@ -1368,6 +1368,7 @@ nl:
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Nooit
|
||||
generate: Uitnodigingslink genereren
|
||||
invalid: Deze uitnodiging is niet geldig
|
||||
invited_by: 'Jij bent uitgenodigd door:'
|
||||
max_uses:
|
||||
one: 1 keer
|
||||
|
@ -1368,6 +1368,7 @@ nn:
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Aldri
|
||||
generate: Lag innbydingslenkje
|
||||
invalid: Denne invitasjonen er ikkje gyldig
|
||||
invited_by: 'Du vart innboden av:'
|
||||
max_uses:
|
||||
one: 1 bruk
|
||||
|
@ -1368,6 +1368,7 @@
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Aldri
|
||||
generate: Generer
|
||||
invalid: Denne invitasjonen er ikke gyldig
|
||||
invited_by: 'Du ble invitert av:'
|
||||
max_uses:
|
||||
one: 1 bruk
|
||||
|
@ -1418,6 +1418,7 @@ pl:
|
||||
'86400': dobie
|
||||
expires_in_prompt: Nigdy
|
||||
generate: Wygeneruj
|
||||
invalid: Niepoprawne zaproszenie
|
||||
invited_by: 'Zostałeś(-aś) zaproszony(-a) przez:'
|
||||
max_uses:
|
||||
few: "%{count} użycia"
|
||||
|
@ -1368,6 +1368,7 @@ pt-BR:
|
||||
'86400': 1 dia
|
||||
expires_in_prompt: Nunca
|
||||
generate: Gerar convite
|
||||
invalid: Este convite não é válido
|
||||
invited_by: 'Você recebeu convite de:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -1368,6 +1368,7 @@ pt-PT:
|
||||
'86400': 1 dia
|
||||
expires_in_prompt: Nunca
|
||||
generate: Gerar hiperligação de convite
|
||||
invalid: Este convite não é válido
|
||||
invited_by: 'Foi convidado por:'
|
||||
max_uses:
|
||||
one: 1 uso
|
||||
|
@ -53,7 +53,7 @@ be:
|
||||
password: Не менш за 8 сімвалаў
|
||||
phrase: Параўнанне адбудзецца нягледзячы на рэгістр тэксту і папярэджанні аб змесціве допісу
|
||||
scopes: Якімі API праграм будзе дазволена карыстацца. Калі вы абярэце найвышэйшы ўзровень, не трэба абіраць асобныя.
|
||||
setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія нядаўна пашырылі(уплывае выключна на будучыя пашырэнні)
|
||||
setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія пашырылі нядаўна (закранае толькі нядаўнія пашырэнні)
|
||||
setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon
|
||||
setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам
|
||||
setting_display_media_default: Хаваць медыя пазначаныя як далікатныя
|
||||
|
@ -592,6 +592,8 @@ sk:
|
||||
title: Ohľadom
|
||||
appearance:
|
||||
title: Vzhľad
|
||||
content_retention:
|
||||
title: Ponechanie obsahu
|
||||
discovery:
|
||||
follow_recommendations: Odporúčania pre nasledovanie
|
||||
profile_directory: Katalóg profilov
|
||||
@ -616,6 +618,7 @@ sk:
|
||||
delete: Vymaž nahratý súbor
|
||||
destroyed_msg: Nahratie bolo zo stránky úspešne vymazané!
|
||||
software_updates:
|
||||
critical_update: Kritické — prosím aktualizuj rýchlo
|
||||
documentation_link: Zisti viac
|
||||
title: Dostupné aktualizácie
|
||||
types:
|
||||
@ -646,6 +649,10 @@ sk:
|
||||
appeal_approved: Namietnuté
|
||||
appeal_rejected: Námietka zamietnutá
|
||||
system_checks:
|
||||
elasticsearch_preset:
|
||||
action: Pozri dokumentáciu
|
||||
elasticsearch_preset_single_node:
|
||||
action: Pozri dokumentáciu
|
||||
rules_check:
|
||||
action: Spravuj serverové pravidlá
|
||||
message_html: Neurčil/a si žiadne serverové pravidlá.
|
||||
@ -925,6 +932,7 @@ sk:
|
||||
'86400': 1 deň
|
||||
expires_in_prompt: Nikdy
|
||||
generate: Vygeneruj
|
||||
invalid: Táto pozvánka je neplatná
|
||||
invited_by: 'Bol/a si pozvaný/á užívateľom:'
|
||||
max_uses:
|
||||
few: "%{count} využití"
|
||||
|
@ -1418,6 +1418,7 @@ sl:
|
||||
'86400': 1 dan
|
||||
expires_in_prompt: Nikoli
|
||||
generate: Ustvari
|
||||
invalid: To povabilo ni veljavno
|
||||
invited_by: 'Povabil/a vas je:'
|
||||
max_uses:
|
||||
few: "%{count} uporabe"
|
||||
|
@ -1362,6 +1362,7 @@ sq:
|
||||
'86400': 1 ditë
|
||||
expires_in_prompt: Kurrë
|
||||
generate: Prodho lidhje ftese
|
||||
invalid: Kjo ftesë s’është e vlefshme
|
||||
invited_by: 'Qetë ftuar nga:'
|
||||
max_uses:
|
||||
one: 1 përdorim
|
||||
|
@ -1393,6 +1393,7 @@ sr-Latn:
|
||||
'86400': 1 dan
|
||||
expires_in_prompt: Nikad
|
||||
generate: Generiši
|
||||
invalid: Ova pozivnica nije važeća
|
||||
invited_by: 'Pozvao Vas je:'
|
||||
max_uses:
|
||||
few: "%{count} korišćenja"
|
||||
|
@ -1393,6 +1393,7 @@ sr:
|
||||
'86400': 1 дан
|
||||
expires_in_prompt: Никад
|
||||
generate: Генериши
|
||||
invalid: Ова позивница није важећа
|
||||
invited_by: 'Позвао Вас је:'
|
||||
max_uses:
|
||||
few: "%{count} коришћења"
|
||||
|
@ -1368,6 +1368,7 @@ sv:
|
||||
'86400': 1 dag
|
||||
expires_in_prompt: Aldrig
|
||||
generate: Skapa
|
||||
invalid: Ogiltig inbjudan
|
||||
invited_by: 'Du blev inbjuden av:'
|
||||
max_uses:
|
||||
one: 1 användning
|
||||
|
@ -1343,6 +1343,7 @@ th:
|
||||
'86400': 1 วัน
|
||||
expires_in_prompt: ไม่เลย
|
||||
generate: สร้างลิงก์เชิญ
|
||||
invalid: คำเชิญนี้ไม่ถูกต้อง
|
||||
invited_by: 'คุณได้รับเชิญโดย:'
|
||||
max_uses:
|
||||
other: "%{count} การใช้งาน"
|
||||
|
@ -1368,6 +1368,7 @@ tr:
|
||||
'86400': 1 gün
|
||||
expires_in_prompt: Asla
|
||||
generate: Davet bağlantısı oluştur
|
||||
invalid: Bu davet geçerli değil
|
||||
invited_by: 'Davet edildiniz:'
|
||||
max_uses:
|
||||
one: 1 kullanım
|
||||
|
@ -1418,6 +1418,7 @@ uk:
|
||||
'86400': 1 день
|
||||
expires_in_prompt: Ніколи
|
||||
generate: Згенерувати
|
||||
invalid: Це запрошення не дійсне
|
||||
invited_by: 'Вас запросив:'
|
||||
max_uses:
|
||||
few: "%{count} використання"
|
||||
|
@ -1343,6 +1343,7 @@ zh-CN:
|
||||
'86400': 1 天后
|
||||
expires_in_prompt: 永不过期
|
||||
generate: 生成邀请链接
|
||||
invalid: 此邀请无效
|
||||
invited_by: 你的邀请人是:
|
||||
max_uses:
|
||||
other: "%{count} 次"
|
||||
|
@ -1343,6 +1343,7 @@ zh-HK:
|
||||
'86400': 1 天後
|
||||
expires_in_prompt: 永不過期
|
||||
generate: 生成邀請連結
|
||||
invalid: 此邀請無效
|
||||
invited_by: 你的邀請人是:
|
||||
max_uses:
|
||||
other: "%{count} 次"
|
||||
|
@ -1345,6 +1345,7 @@ zh-TW:
|
||||
'86400': 1 天後
|
||||
expires_in_prompt: 永不過期
|
||||
generate: 建立邀請連結
|
||||
invalid: 此邀請是無效的
|
||||
invited_by: 您的邀請人是:
|
||||
max_uses:
|
||||
other: "%{count} 則"
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
test: /\.svg$/,
|
||||
include: /node_modules\/@material-symbols/,
|
||||
include: [/node_modules\/@material-symbols/, /svg-icons/],
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: [
|
||||
{
|
||||
|
@ -179,7 +179,7 @@
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/react-immutable-proptypes": "^2.1.0",
|
||||
"@types/react-motion": "^0.0.36",
|
||||
"@types/react-motion": "^0.0.37",
|
||||
"@types/react-overlays": "^3.1.0",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
@ -203,7 +203,7 @@
|
||||
"eslint-plugin-formatjs": "^4.10.1",
|
||||
"eslint-plugin-import": "~2.29.0",
|
||||
"eslint-plugin-jsdoc": "^46.1.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.7.1",
|
||||
"eslint-plugin-jsx-a11y": "~6.8.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-promise": "~6.1.1",
|
||||
"eslint-plugin-react": "~7.33.0",
|
||||
@ -245,5 +245,5 @@
|
||||
"*.{js,jsx,ts,tsx}": "eslint --fix",
|
||||
"*.{css,scss}": "stylelint --fix"
|
||||
},
|
||||
"packageManager": "yarn@4.0.1"
|
||||
"packageManager": "yarn@4.0.2"
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ RSpec.describe ActivityPub::InboxesController do
|
||||
|
||||
before do
|
||||
allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
|
||||
allow_any_instance_of(Account).to receive(:local_followers_hash).and_return('somehash')
|
||||
allow(remote_account).to receive(:local_followers_hash).and_return('somehash')
|
||||
|
||||
request.headers['Collection-Synchronization'] = synchronization_header
|
||||
post :create, body: '{}'
|
||||
|
@ -20,8 +20,7 @@ RSpec.describe Admin::AccountsController do
|
||||
it 'filters with parameters' do
|
||||
account_filter = instance_double(AccountFilter, results: Account.all)
|
||||
allow(AccountFilter).to receive(:new).and_return(account_filter)
|
||||
|
||||
get :index, params: {
|
||||
params = {
|
||||
origin: 'local',
|
||||
by_domain: 'domain',
|
||||
status: 'active',
|
||||
@ -31,17 +30,9 @@ RSpec.describe Admin::AccountsController do
|
||||
ip: '0.0.0.42',
|
||||
}
|
||||
|
||||
expect(AccountFilter).to have_received(:new) do |params|
|
||||
h = params.to_h
|
||||
get :index, params: params
|
||||
|
||||
expect(h[:origin]).to eq 'local'
|
||||
expect(h[:by_domain]).to eq 'domain'
|
||||
expect(h[:status]).to eq 'active'
|
||||
expect(h[:username]).to eq 'username'
|
||||
expect(h[:display_name]).to eq 'display name'
|
||||
expect(h[:email]).to eq 'local-part@domain'
|
||||
expect(h[:ip]).to eq '0.0.0.42'
|
||||
end
|
||||
expect(AccountFilter).to have_received(:new).with(hash_including(params))
|
||||
end
|
||||
|
||||
it 'paginates accounts' do
|
||||
@ -236,7 +227,8 @@ RSpec.describe Admin::AccountsController do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(ResolveAccountService).to receive(:call)
|
||||
service = instance_double(ResolveAccountService, call: nil)
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
end
|
||||
|
||||
context 'when user is admin' do
|
||||
|
@ -13,12 +13,20 @@ describe Admin::ResetsController do
|
||||
|
||||
describe 'POST #create' do
|
||||
it 'redirects to admin accounts page' do
|
||||
expect_any_instance_of(User).to receive(:send_reset_password_instructions) do |value|
|
||||
expect(value.account_id).to eq account.id
|
||||
end
|
||||
|
||||
expect do
|
||||
post :create, params: { account_id: account.id }
|
||||
end.to change(Devise.mailer.deliveries, :size).by(2)
|
||||
|
||||
expect(Devise.mailer.deliveries).to have_attributes(
|
||||
first: have_attributes(
|
||||
to: include(account.user.email),
|
||||
subject: I18n.t('devise.mailer.password_change.subject')
|
||||
),
|
||||
last: have_attributes(
|
||||
to: include(account.user.email),
|
||||
subject: I18n.t('devise.mailer.reset_password_instructions.subject')
|
||||
)
|
||||
)
|
||||
expect(response).to redirect_to(admin_account_path(account.id))
|
||||
end
|
||||
end
|
||||
|
@ -126,7 +126,7 @@ RSpec.describe Auth::SessionsController do
|
||||
let!(:previous_login) { Fabricate(:login_activity, user: user, ip: previous_ip) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip)
|
||||
allow(controller.request).to receive(:remote_ip).and_return(current_ip)
|
||||
user.update(current_sign_in_at: 1.month.ago)
|
||||
post :create, params: { user: { email: user.email, password: user.password } }
|
||||
end
|
||||
@ -279,7 +279,9 @@ RSpec.describe Auth::SessionsController do
|
||||
|
||||
context 'when the server has an decryption error' do
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError)
|
||||
allow(user).to receive(:validate_and_consume_otp!).with(user.current_otp).and_raise(OpenSSL::Cipher::CipherError)
|
||||
allow(User).to receive(:find_by).with(id: user.id).and_return(user)
|
||||
|
||||
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
|
||||
end
|
||||
|
||||
|
@ -61,6 +61,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
|
||||
it 'renders page with success' do
|
||||
prepare_user_otp_generation
|
||||
prepare_user_otp_consumption
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
|
||||
expect do
|
||||
post :create,
|
||||
@ -75,30 +76,28 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
|
||||
end
|
||||
|
||||
def prepare_user_otp_generation
|
||||
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value|
|
||||
expect(value).to eq user
|
||||
otp_backup_codes
|
||||
end
|
||||
allow(user)
|
||||
.to receive(:generate_otp_backup_codes!)
|
||||
.and_return(otp_backup_codes)
|
||||
end
|
||||
|
||||
def prepare_user_otp_consumption
|
||||
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options|
|
||||
expect(value).to eq user
|
||||
expect(code).to eq '123456'
|
||||
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' })
|
||||
true
|
||||
end
|
||||
options = { otp_secret: 'thisisasecretforthespecofnewview' }
|
||||
allow(user)
|
||||
.to receive(:validate_and_consume_otp!)
|
||||
.with('123456', options)
|
||||
.and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when creation fails' do
|
||||
subject do
|
||||
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options|
|
||||
expect(value).to eq user
|
||||
expect(code).to eq '123456'
|
||||
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' })
|
||||
false
|
||||
end
|
||||
options = { otp_secret: 'thisisasecretforthespecofnewview' }
|
||||
allow(user)
|
||||
.to receive(:validate_and_consume_otp!)
|
||||
.with('123456', options)
|
||||
.and_return(false)
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
|
||||
expect do
|
||||
post :create,
|
||||
|
@ -9,10 +9,8 @@ describe Settings::TwoFactorAuthentication::RecoveryCodesController do
|
||||
it 'updates the codes and shows them on a view when signed in' do
|
||||
user = Fabricate(:user)
|
||||
otp_backup_codes = user.generate_otp_backup_codes!
|
||||
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value|
|
||||
expect(value).to eq user
|
||||
otp_backup_codes
|
||||
end
|
||||
allow(user).to receive(:generate_otp_backup_codes!).and_return(otp_backup_codes)
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
|
||||
sign_in user, scope: :user
|
||||
post :create, session: { challenge_passed_at: Time.now.utc }
|
||||
|
@ -64,8 +64,11 @@ describe Request do
|
||||
end
|
||||
|
||||
it 'closes underlying connection' do
|
||||
expect_any_instance_of(HTTP::Client).to receive(:close)
|
||||
allow(subject.send(:http_client)).to receive(:close)
|
||||
|
||||
expect { |block| subject.perform(&block) }.to yield_control
|
||||
|
||||
expect(subject.send(:http_client)).to have_received(:close)
|
||||
end
|
||||
|
||||
it 'returns response which implements body_with_limit' do
|
||||
|
@ -23,7 +23,8 @@ describe StatusFilter do
|
||||
|
||||
context 'when status policy does not allow show' do
|
||||
it 'filters the status' do
|
||||
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
|
||||
policy = instance_double(StatusPolicy, show?: false)
|
||||
allow(StatusPolicy).to receive(:new).and_return(policy)
|
||||
|
||||
expect(filter).to be_filtered
|
||||
end
|
||||
@ -74,7 +75,8 @@ describe StatusFilter do
|
||||
|
||||
context 'when status policy does not allow show' do
|
||||
it 'filters the status' do
|
||||
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
|
||||
policy = instance_double(StatusPolicy, show?: false)
|
||||
allow(StatusPolicy).to receive(:new).and_return(policy)
|
||||
|
||||
expect(filter).to be_filtered
|
||||
end
|
||||
|
@ -209,9 +209,13 @@ RSpec.describe Account do
|
||||
expect(account.refresh!).to be_nil
|
||||
end
|
||||
|
||||
it 'calls not ResolveAccountService#call' do
|
||||
expect_any_instance_of(ResolveAccountService).to_not receive(:call).with(acct)
|
||||
it 'does not call ResolveAccountService#call' do
|
||||
service = instance_double(ResolveAccountService, call: nil)
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
|
||||
account.refresh!
|
||||
|
||||
expect(service).to_not have_received(:call).with(acct)
|
||||
end
|
||||
end
|
||||
|
||||
@ -219,8 +223,12 @@ RSpec.describe Account do
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
it 'calls ResolveAccountService#call' do
|
||||
expect_any_instance_of(ResolveAccountService).to receive(:call).with(acct).once
|
||||
service = instance_double(ResolveAccountService, call: nil)
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
|
||||
account.refresh!
|
||||
|
||||
expect(service).to have_received(:call).with(acct).once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,7 +52,8 @@ RSpec.describe Setting do
|
||||
before do
|
||||
allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
|
||||
allow(described_class).to receive(:default_settings).and_return(default_settings)
|
||||
allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
|
||||
settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
|
||||
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
|
||||
Rails.cache.delete(cache_key)
|
||||
end
|
||||
|
||||
@ -128,7 +129,8 @@ RSpec.describe Setting do
|
||||
|
||||
describe '.all_as_records' do
|
||||
before do
|
||||
allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
|
||||
settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
|
||||
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
|
||||
allow(described_class).to receive(:default_settings).and_return(default_settings)
|
||||
end
|
||||
|
||||
|
@ -5,6 +5,37 @@ require 'rails_helper'
|
||||
RSpec.describe Webhook do
|
||||
let(:webhook) { Fabricate(:webhook) }
|
||||
|
||||
describe 'Validations' do
|
||||
it 'requires presence of events' do
|
||||
record = described_class.new(events: nil)
|
||||
record.valid?
|
||||
|
||||
expect(record).to model_have_error_on_field(:events)
|
||||
end
|
||||
|
||||
it 'requires non-empty events value' do
|
||||
record = described_class.new(events: [])
|
||||
record.valid?
|
||||
|
||||
expect(record).to model_have_error_on_field(:events)
|
||||
end
|
||||
|
||||
it 'requires valid events value from EVENTS' do
|
||||
record = described_class.new(events: ['account.invalid'])
|
||||
record.valid?
|
||||
|
||||
expect(record).to model_have_error_on_field(:events)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Normalizations' do
|
||||
it 'cleans up events values' do
|
||||
record = described_class.new(events: ['account.approved', 'account.created ', ''])
|
||||
|
||||
expect(record.events).to eq(%w(account.approved account.created))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#rotate_secret!' do
|
||||
it 'changes the secret' do
|
||||
previous_value = webhook.secret
|
||||
|
@ -102,17 +102,25 @@ describe 'GET /api/v1/accounts/relationships' do
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data on cached requests too' do
|
||||
subject
|
||||
subject
|
||||
it 'returns JSON with correct data on previously cached requests' do
|
||||
# Initial request including multiple accounts in params
|
||||
get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id, lewis.id] }
|
||||
expect(body_as_json.size).to eq(2)
|
||||
|
||||
# Subsequent request with different id, should override cache from first request
|
||||
get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id] }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:showing_reblogs]).to be true
|
||||
expect(body_as_json)
|
||||
.to be_an(Enumerable)
|
||||
.and have_attributes(
|
||||
size: 1,
|
||||
first: hash_including(
|
||||
following: true,
|
||||
showing_reblogs: true
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data after change too' do
|
||||
|
43
spec/requests/api/v1/csp_spec.rb
Normal file
43
spec/requests/api/v1/csp_spec.rb
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'API namespace minimal Content-Security-Policy' do
|
||||
before { stub_tests_controller }
|
||||
|
||||
after { Rails.application.reload_routes! }
|
||||
|
||||
it 'returns the correct CSP headers' do
|
||||
get '/api/v1/tests'
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.headers['Content-Security-Policy']).to eq(minimal_csp_headers)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stub_tests_controller
|
||||
stub_const('Api::V1::TestsController', api_tests_controller)
|
||||
|
||||
Rails.application.routes.draw do
|
||||
get '/api/v1/tests', to: 'api/v1/tests#index'
|
||||
end
|
||||
end
|
||||
|
||||
def api_tests_controller
|
||||
Class.new(Api::BaseController) do
|
||||
def index
|
||||
head 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_signed_in? = false
|
||||
def current_user = nil
|
||||
end
|
||||
end
|
||||
|
||||
def minimal_csp_headers
|
||||
"default-src 'none'; frame-ancestors 'none'; form-action 'none'"
|
||||
end
|
||||
end
|
@ -76,7 +76,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
|
||||
let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }
|
||||
|
||||
it 'does not process payload if no signature exists' do
|
||||
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
|
||||
signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
|
||||
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
|
||||
allow(ActivityPub::Activity).to receive(:factory)
|
||||
|
||||
subject.call(json, forwarder)
|
||||
@ -87,7 +88,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
|
||||
it 'processes payload with actor if valid signature exists' do
|
||||
payload['signature'] = { 'type' => 'RsaSignature2017' }
|
||||
|
||||
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor)
|
||||
signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: actor)
|
||||
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
|
||||
allow(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))
|
||||
|
||||
subject.call(json, forwarder)
|
||||
@ -98,7 +100,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
|
||||
it 'does not process payload if invalid signature exists' do
|
||||
payload['signature'] = { 'type' => 'RsaSignature2017' }
|
||||
|
||||
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
|
||||
signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
|
||||
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
|
||||
allow(ActivityPub::Activity).to receive(:factory)
|
||||
|
||||
subject.call(json, forwarder)
|
||||
|
@ -11,7 +11,8 @@ describe ActivityPub::DeliveryWorker do
|
||||
let(:payload) { 'test' }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash')
|
||||
allow(sender).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash')
|
||||
allow(Account).to receive(:find).with(sender.id).and_return(sender)
|
||||
end
|
||||
|
||||
describe 'perform' do
|
||||
|
@ -23,8 +23,8 @@ describe Web::PushNotificationWorker do
|
||||
|
||||
describe 'perform' do
|
||||
before do
|
||||
allow_any_instance_of(subscription.class).to receive(:contact_email).and_return(contact_email)
|
||||
allow_any_instance_of(subscription.class).to receive(:vapid_key).and_return(vapid_key)
|
||||
allow(subscription).to receive_messages(contact_email: contact_email, vapid_key: vapid_key)
|
||||
allow(Web::PushSubscription).to receive(:find).with(subscription.id).and_return(subscription)
|
||||
allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
|
||||
allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user