Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
ed6acbf542
32 changed files with 509 additions and 275 deletions
|
@ -5,7 +5,6 @@ class AntennasController < ApplicationController
|
|||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_antenna, only: [:edit, :update, :destroy]
|
||||
before_action :set_lists, only: [:new, :edit]
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
|
@ -54,16 +53,12 @@ class AntennasController < ApplicationController
|
|||
@antenna = current_account.antennas.find(params[:id])
|
||||
end
|
||||
|
||||
def set_lists
|
||||
@lists = current_account.owned_lists
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:antenna).permit(:title, :list, :available, :insert_feeds, :stl, :expires_in, :with_media_only, :ignore_reblog, :keywords_raw, :exclude_keywords_raw, :domains_raw, :exclude_domains_raw, :accounts_raw, :exclude_accounts_raw, :tags_raw, :exclude_tags_raw)
|
||||
params.require(:antenna).permit(:title, :available, :expires_in)
|
||||
end
|
||||
|
||||
def thin_resource_params
|
||||
params.require(:antenna).permit(:title, :list)
|
||||
params.require(:antenna).permit(:title)
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
|
|
@ -9,7 +9,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||
def index
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,7 +7,9 @@ class Api::V1::BookmarksController < Api::BaseController
|
|||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,7 +7,9 @@ class Api::V1::EmojiReactionsController < Api::BaseController
|
|||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,7 +7,9 @@ class Api::V1::FavouritesController < Api::BaseController
|
|||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -9,7 +9,9 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController
|
|||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -9,11 +9,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
|||
with_read_replica do
|
||||
@statuses = load_statuses
|
||||
@relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
@emoji_reactions = EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
render json: @statuses,
|
||||
each_serializer: REST::StatusSerializer,
|
||||
relationships: @relationships,
|
||||
emoji_reaction_permitted_account_ids: @emoji_reactions,
|
||||
status: account_home_feed.regenerating? ? 206 : 200
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
|||
def show
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,7 +8,9 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
|||
def show
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -585,6 +585,10 @@ class Status extends ImmutablePureComponent {
|
|||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = !status.get('hidden')
|
||||
|
||||
const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' title='Limited' /></span> : null;
|
||||
const withReference = status.get('status_references_count') > 0 ? <span className='status__visibility-icon'><Icon id='link' title='Reference' /></span> : null;
|
||||
const withExpiration = status.get('expires_at') ? <span className='status__visibility-icon'><Icon id='clock-o' title='Expiration' /></span> : null;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility_ex')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
|
@ -596,6 +600,9 @@ class Status extends ImmutablePureComponent {
|
|||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
{withReference}
|
||||
{withExpiration}
|
||||
{withLimited}
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</a>
|
||||
|
|
|
@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||
|
||||
import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
|
||||
import Button from 'mastodon/components/button';
|
||||
|
|
|
@ -35,7 +35,7 @@ class StatusCacheHydrator
|
|||
payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists?
|
||||
payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id
|
||||
payload[:reblog][:filtered] = payload[:filtered]
|
||||
payload[:reblog][:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account)
|
||||
payload[:reblog][:emoji_reactions] = @status.reblog.emoji_reactions_grouped_by_name(account)
|
||||
|
||||
if payload[:reblog][:poll]
|
||||
if @status.reblog.account_id == account_id
|
||||
|
|
|
@ -92,142 +92,6 @@ class Antenna < ApplicationRecord
|
|||
context
|
||||
end
|
||||
|
||||
def list=(list_id)
|
||||
list_id = list_id.to_i if list_id.is_a?(String)
|
||||
if list_id.is_a?(Numeric)
|
||||
self[:list_id] = list_id
|
||||
else
|
||||
self[:list] = list_id
|
||||
end
|
||||
end
|
||||
|
||||
def keywords_raw
|
||||
return '' if keywords.blank?
|
||||
|
||||
keywords.join("\n")
|
||||
end
|
||||
|
||||
def keywords_raw=(raw)
|
||||
keywords = raw.split(/\R/).filter { |r| r.present? && r.length >= 2 }.uniq
|
||||
self[:keywords] = keywords
|
||||
self[:any_keywords] = keywords.none?
|
||||
end
|
||||
|
||||
def exclude_keywords_raw
|
||||
return '' if exclude_keywords.blank?
|
||||
|
||||
exclude_keywords.join("\n")
|
||||
end
|
||||
|
||||
def exclude_keywords_raw=(raw)
|
||||
exclude_keywords = raw.split(/\R/).filter(&:present?).uniq
|
||||
self[:exclude_keywords] = exclude_keywords
|
||||
end
|
||||
|
||||
def tags_raw
|
||||
antenna_tags.where(exclude: false).map { |tag| tag.tag.name }.join("\n")
|
||||
end
|
||||
|
||||
def tags_raw=(raw)
|
||||
return if tags_raw == raw
|
||||
|
||||
tag_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('#') ? r[1..] : r }.uniq
|
||||
|
||||
antenna_tags.where(exclude: false).destroy_all
|
||||
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||
antenna_tags.create!(tag: tag, exclude: false)
|
||||
end
|
||||
self[:any_tags] = tag_names.none?
|
||||
end
|
||||
|
||||
def exclude_tags_raw
|
||||
return '' if exclude_tags.blank?
|
||||
|
||||
Tag.where(id: exclude_tags).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def exclude_tags_raw=(raw)
|
||||
return if exclude_tags_raw == raw
|
||||
|
||||
tags = []
|
||||
tag_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('#') ? r[1..] : r }.uniq
|
||||
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||
tags << tag.id
|
||||
end
|
||||
self[:exclude_tags] = tags
|
||||
end
|
||||
|
||||
def domains_raw
|
||||
antenna_domains.where(exclude: false).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def domains_raw=(raw)
|
||||
return if domains_raw == raw
|
||||
|
||||
domain_names = raw.split(/\R/).filter(&:present?).uniq
|
||||
|
||||
antenna_domains.where(exclude: false).destroy_all
|
||||
domain_names.each do |domain|
|
||||
antenna_domains.create!(name: domain, exclude: false)
|
||||
end
|
||||
self[:any_domains] = domain_names.none?
|
||||
end
|
||||
|
||||
def exclude_domains_raw
|
||||
return '' if exclude_domains.blank?
|
||||
|
||||
exclude_domains.join("\n")
|
||||
end
|
||||
|
||||
def exclude_domains_raw=(raw)
|
||||
return if exclude_domains_raw == raw
|
||||
|
||||
domain_names = raw.split(/\R/).filter(&:present?).uniq
|
||||
self[:exclude_domains] = domain_names
|
||||
end
|
||||
|
||||
def accounts_raw
|
||||
antenna_accounts.where(exclude: false).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||
end
|
||||
|
||||
def accounts_raw=(raw)
|
||||
return if accounts_raw == raw
|
||||
|
||||
account_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('@') ? r[1..] : r }.uniq
|
||||
|
||||
hit = false
|
||||
antenna_accounts.where(exclude: false).destroy_all
|
||||
account_names.each do |name|
|
||||
username, domain = name.split('@')
|
||||
account = Account.find_by(username: username, domain: domain)
|
||||
if account.present?
|
||||
antenna_accounts.create!(account: account, exclude: false)
|
||||
hit = true
|
||||
end
|
||||
end
|
||||
self[:any_accounts] = !hit
|
||||
end
|
||||
|
||||
def exclude_accounts_raw
|
||||
return '' if exclude_accounts.blank?
|
||||
|
||||
Account.where(id: exclude_accounts).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||
end
|
||||
|
||||
def exclude_accounts_raw=(raw)
|
||||
return if exclude_accounts_raw == raw
|
||||
|
||||
account_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('@') ? r[1..] : r }.uniq
|
||||
|
||||
accounts = []
|
||||
account_names.each do |name|
|
||||
username, domain = name.split('@')
|
||||
account = Account.find_by(username: username, domain: domain)
|
||||
accounts << account.id if account.present?
|
||||
end
|
||||
self[:exclude_accounts] = accounts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_limit
|
||||
|
|
|
@ -28,7 +28,8 @@ class PublicFeed
|
|||
scope.merge!(account_filters_scope) if account?
|
||||
scope.merge!(media_only_scope) if media_only?
|
||||
scope.merge!(language_scope) if account&.chosen_languages.present?
|
||||
scope.merge!(anonymous_scope) unless account?
|
||||
# scope.merge!(anonymous_scope) unless account?
|
||||
scope = to_anonymous_scope(scope) unless account?
|
||||
|
||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||
end
|
||||
|
@ -105,6 +106,10 @@ class PublicFeed
|
|||
local_only? ? Status.where(visibility: [:public, :public_unlisted]) : Status.where(visibility: :public)
|
||||
end
|
||||
|
||||
def to_anonymous_scope(scope)
|
||||
scope.where.not(visibility: :login)
|
||||
end
|
||||
|
||||
def account_filters_scope
|
||||
Status.not_excluded_by_account(account).tap do |scope|
|
||||
scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only?
|
||||
|
|
|
@ -173,6 +173,8 @@ class Status < ApplicationRecord
|
|||
:tags,
|
||||
:preview_cards,
|
||||
:preloadable_poll,
|
||||
:reference_objects,
|
||||
:scheduled_expiration_status,
|
||||
account: [:account_stat, user: :role],
|
||||
active_mentions: { account: :account_stat },
|
||||
reblog: [
|
||||
|
@ -183,6 +185,8 @@ class Status < ApplicationRecord
|
|||
:conversation,
|
||||
:status_stat,
|
||||
:preloadable_poll,
|
||||
:reference_objects,
|
||||
:scheduled_expiration_status,
|
||||
account: [:account_stat, user: :role],
|
||||
active_mentions: { account: :account_stat },
|
||||
],
|
||||
|
@ -353,21 +357,30 @@ class Status < ApplicationRecord
|
|||
return [] if account.present? && !self.account.show_emoji_reaction?(account)
|
||||
return [] if account.nil? && !options[:force] && self.account.emoji_reaction_policy != :allow
|
||||
|
||||
permitted_account_ids = options[:permitted_account_ids]
|
||||
|
||||
(Oj.load(status_stat&.emoji_reactions || '', mode: :strict) || []).tap do |emoji_reactions|
|
||||
if account.present?
|
||||
remove_emoji_reactions = []
|
||||
public_emoji_reactions = []
|
||||
|
||||
emoji_reactions.each do |emoji_reaction|
|
||||
emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s)
|
||||
emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s)
|
||||
|
||||
accounts = Account.where(id: emoji_reaction['account_ids'], silenced_at: nil, suspended_at: nil)
|
||||
accounts = accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.excluded_from_timeline_domains.size.positive?
|
||||
emoji_reaction['account_ids'] = accounts.pluck(:id).map(&:to_s)
|
||||
accounts = []
|
||||
if permitted_account_ids
|
||||
emoji_reaction['account_ids'] = emoji_reaction['account_ids'] & permitted_account_ids.map(&:to_s)
|
||||
else
|
||||
accounts = Account.where(id: emoji_reaction['account_ids'], silenced_at: nil, suspended_at: nil)
|
||||
accounts = accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.excluded_from_timeline_domains.size.positive?
|
||||
emoji_reaction['account_ids'] = accounts.pluck(:id).map(&:to_s)
|
||||
end
|
||||
|
||||
emoji_reaction['count'] = emoji_reaction['account_ids'].size
|
||||
remove_emoji_reactions << emoji_reaction if emoji_reaction['count'] <= 0
|
||||
public_emoji_reactions << emoji_reaction if (emoji_reaction['count']).positive?
|
||||
end
|
||||
emoji_reactions - remove_emoji_reactions
|
||||
|
||||
public_emoji_reactions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -459,6 +472,11 @@ class Status < ApplicationRecord
|
|||
EmojiReaction.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |e, h| h[e.status_id] = true }
|
||||
end
|
||||
|
||||
def emoji_reaction_allows_map(status_ids, account_id)
|
||||
my_account = Account.find_by(id: account_id)
|
||||
Status.where(id: status_ids).pluck(:account_id).uniq.index_with { |a| Account.find_by(id: a).show_emoji_reaction?(my_account) }
|
||||
end
|
||||
|
||||
def reload_stale_associations!(cached_items)
|
||||
account_ids = []
|
||||
|
||||
|
|
|
@ -18,8 +18,16 @@ class StatusReference < ApplicationRecord
|
|||
has_one :notification, as: :activity, dependent: :destroy
|
||||
|
||||
validate :validate_status_visibilities
|
||||
after_commit :reset_parent_cache
|
||||
|
||||
private
|
||||
|
||||
def validate_status_visibilities
|
||||
raise Mastodon::ValidationError, I18n.t('status_references.errors.invalid_status_visibilities') if [:public, :public_unlisted, :unlisted, :login].exclude?(target_status.visibility.to_sym)
|
||||
end
|
||||
|
||||
def reset_parent_cache
|
||||
Rails.cache.delete("statuses/#{status_id}")
|
||||
Rails.cache.delete("statuses/#{target_status_id}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,8 @@ class TagFeed < PublicFeed
|
|||
scope.merge!(remote_only_scope) if remote_only? || hide_local_users?
|
||||
scope.merge!(account_filters_scope) if account?
|
||||
scope.merge!(media_only_scope) if media_only?
|
||||
scope.merge!(anonymous_scope) unless account?
|
||||
# scope.merge!(anonymous_scope) unless account?
|
||||
scope = to_anonymous_scope(scope) unless account?
|
||||
|
||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||
end
|
||||
|
|
22
app/presenters/emoji_reaction_accounts_presenter.rb
Normal file
22
app/presenters/emoji_reaction_accounts_presenter.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EmojiReactionAccountsPresenter
|
||||
attr_reader :permitted_account_ids
|
||||
|
||||
def initialize(statuses, current_account_id = nil, **_options)
|
||||
@current_account_id = current_account_id
|
||||
|
||||
statuses = statuses.compact
|
||||
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
|
||||
emoji_reactions = EmojiReaction.where(status_id: status_ids)
|
||||
account_ids = emoji_reactions.pluck(:account_id).uniq
|
||||
|
||||
permitted_accounts = Account.where(id: account_ids, silenced_at: nil, suspended_at: nil)
|
||||
if current_account_id.present?
|
||||
account = Account.find(current_account_id)
|
||||
permitted_accounts = permitted_accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.present? && account.excluded_from_timeline_domains.size.positive?
|
||||
end
|
||||
|
||||
@permitted_account_ids = permitted_accounts.pluck(:id)
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ class StatusRelationshipsPresenter
|
|||
PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted login private).freeze
|
||||
|
||||
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
|
||||
:bookmarks_map, :filters_map, :emoji_reactions_map, :attributes_map
|
||||
:bookmarks_map, :filters_map, :emoji_reactions_map, :attributes_map, :emoji_reaction_allows_map
|
||||
|
||||
def initialize(statuses, current_account_id = nil, **options)
|
||||
@current_account_id = current_account_id
|
||||
|
@ -17,6 +17,7 @@ class StatusRelationshipsPresenter
|
|||
@pins_map = {}
|
||||
@filters_map = {}
|
||||
@emoji_reactions_map = {}
|
||||
@emoji_reaction_allows_map = nil
|
||||
else
|
||||
statuses = statuses.compact
|
||||
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
|
||||
|
@ -30,6 +31,7 @@ class StatusRelationshipsPresenter
|
|||
@mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {})
|
||||
@pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {})
|
||||
@emoji_reactions_map = Status.emoji_reactions_map(status_ids, current_account_id).merge(options[:emoji_reactions_map] || {})
|
||||
@emoji_reaction_allows_map = Status.emoji_reaction_allows_map(status_ids, current_account_id).merge(options[:emoji_reaction_allows_map] || {})
|
||||
@attributes_map = options[:attributes_map] || {}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
attribute :bookmarked, if: :current_user?
|
||||
attribute :pinned, if: :pinnable?
|
||||
attribute :reactions, if: :reactions?
|
||||
attribute :expires_at, if: :will_expire?
|
||||
has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user?
|
||||
|
||||
attribute :content, unless: :source_requested?
|
||||
|
@ -122,7 +123,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def emoji_reactions
|
||||
object.emoji_reactions_grouped_by_name(current_user&.account)
|
||||
show_emoji_reaction? ? object.emoji_reactions_grouped_by_name(current_user&.account, permitted_account_ids: emoji_reaction_permitted_account_ids) : []
|
||||
end
|
||||
|
||||
def emoji_reactions_count
|
||||
|
@ -131,7 +132,17 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
|
||||
object.account.emoji_reaction_policy == :allow ? object.emoji_reactions_count : 0
|
||||
else
|
||||
object.account.show_emoji_reaction?(current_user.account) ? object.emoji_reactions_count : 0
|
||||
show_emoji_reaction? ? object.emoji_reactions_count : 0
|
||||
end
|
||||
end
|
||||
|
||||
def show_emoji_reaction?
|
||||
if relationships
|
||||
return true if relationships.emoji_reaction_allows_map.nil?
|
||||
|
||||
relationships.emoji_reaction_allows_map[object.account_id] || false
|
||||
else
|
||||
object.account.show_emoji_reaction?(current_user.account)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -205,12 +216,24 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
object.active_mentions.to_a.sort_by(&:id)
|
||||
end
|
||||
|
||||
def will_expire?
|
||||
object.scheduled_expiration_status.present?
|
||||
end
|
||||
|
||||
def expires_at
|
||||
object.scheduled_expiration_status.scheduled_at
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relationships
|
||||
instance_options && instance_options[:relationships]
|
||||
end
|
||||
|
||||
def emoji_reaction_permitted_account_ids
|
||||
current_user.present? && instance_options && instance_options[:emoji_reaction_permitted_account_ids]&.permitted_account_ids
|
||||
end
|
||||
|
||||
class ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :name, :website
|
||||
|
||||
|
|
|
@ -5,13 +5,14 @@ class ProcessReferencesService < BaseService
|
|||
|
||||
DOMAIN = ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil)
|
||||
REFURL_EXP = /(RT|QT|BT|RN|RE)((:|;)?\s+|:|;)(#{URI::DEFAULT_PARSER.make_regexp(%w(http https))})/
|
||||
MAX_REFERENCES = 5
|
||||
|
||||
def call(status, reference_parameters, urls: nil)
|
||||
@status = status
|
||||
@reference_parameters = reference_parameters || []
|
||||
@urls = urls || []
|
||||
|
||||
old_references
|
||||
@references_count = old_references.size
|
||||
|
||||
return unless added_references.size.positive? || removed_references.size.positive?
|
||||
|
||||
|
@ -63,6 +64,9 @@ class ProcessReferencesService < BaseService
|
|||
statuses.each do |status|
|
||||
@added_objects << @status.reference_objects.new(target_status: status)
|
||||
status.increment_count!(:status_referred_by_count)
|
||||
@references_count += 1
|
||||
|
||||
break if @references_count >= MAX_REFERENCES
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -85,6 +89,7 @@ class ProcessReferencesService < BaseService
|
|||
@status.reference_objects.where(target_status: statuses).destroy_all
|
||||
statuses.each do |status|
|
||||
status.decrement_count!(:status_referred_by_count)
|
||||
@references_count -= 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,57 +8,4 @@
|
|||
= f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt')
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= f.input :list, collection: lists, wrapper: :with_label, label_method: lambda { |list| list.title }, label: t('antennas.edit.list'), selected: f.object.list&.id, hint: false, include_blank: '[Insert to Home]'
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false
|
||||
|
||||
.fields-row
|
||||
= f.input :insert_feeds, wrapper: :with_label, label: t('antennas.edit.insert_feeds')
|
||||
.fields-row
|
||||
= f.input :stl, wrapper: :with_label, label: t('antennas.edit.stl'), hint: t('antennas.edit.stl_hint')
|
||||
|
||||
%hr.spacer/
|
||||
%p.hint= t 'antennas.edit.hint'
|
||||
%hr.spacer/
|
||||
|
||||
%h4= t('antennas.contexts.domain')
|
||||
%p.hint= t 'antennas.edit.domains_hint'
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.domains_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_domains_raw')
|
||||
|
||||
%h4= t('antennas.contexts.account')
|
||||
%p.hint= t 'antennas.edit.accounts_hint'
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.accounts_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_accounts_raw')
|
||||
|
||||
%h4= t('antennas.contexts.tag')
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.tags_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_tags_raw')
|
||||
|
||||
%h4= t('antennas.contexts.keyword')
|
||||
%p.hint= t 'antennas.edit.keywords_hint'
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.keywords_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_keywords_raw')
|
||||
|
||||
%hr.spacer/
|
||||
.fields-group
|
||||
= f.input :with_media_only, wrapper: :with_label, label: t('antennas.edit.with_media_only'), hint: false
|
||||
.fields-group
|
||||
= f.input :ignore_reblog, wrapper: :with_label, label: t('antennas.edit.ignore_reblog'), hint: false
|
||||
= f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false
|
||||
|
|
|
@ -9,7 +9,6 @@ class ActivityPub::FetchInstanceInfoWorker
|
|||
sidekiq_options queue: 'push', retry: 2
|
||||
|
||||
class Error < StandardError; end
|
||||
class GoneError < Error; end
|
||||
class RequestError < Error; end
|
||||
class DeadError < Error; end
|
||||
|
||||
|
@ -68,9 +67,7 @@ class ActivityPub::FetchInstanceInfoWorker
|
|||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
|
||||
|
||||
body_to_json(response.body_with_limit)
|
||||
elsif response.code == 410
|
||||
raise ActivityPub::FetchInstanceInfoWorker::GoneError, "#{@instance.domain} is gone from the server"
|
||||
elsif response.code == 404
|
||||
elsif [400, 401, 403, 404, 410].include?(response.code)
|
||||
raise ActivityPub::FetchInstanceInfoWorker::DeadError, "Request for #{@instance.domain} returned HTTP #{response.code}"
|
||||
else
|
||||
raise ActivityPub::FetchInstanceInfoWorker::RequestError, "Request for #{@instance.domain} returned HTTP #{response.code}"
|
||||
|
|
|
@ -5,7 +5,7 @@ module Mastodon
|
|||
module_function
|
||||
|
||||
def kmyblue_major
|
||||
3
|
||||
4
|
||||
end
|
||||
|
||||
def kmyblue_minor
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"@formatjs/intl-pluralrules": "^5.2.2",
|
||||
"@gamestdio/websocket": "^0.3.2",
|
||||
"@github/webauthn-json": "^2.1.1",
|
||||
"@hello-pangea/dnd": "^16.3.0",
|
||||
"@material-design-icons/svg": "^0.14.10",
|
||||
"@rails/ujs": "^7.0.6",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
|
@ -100,7 +101,6 @@
|
|||
"prop-types": "^15.8.1",
|
||||
"punycode": "^2.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hotkeys": "^1.1.4",
|
||||
|
|
92
spec/controllers/api/v1/circles/accounts_controller_spec.rb
Normal file
92
spec/controllers/api/v1/circles/accounts_controller_spec.rb
Normal file
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::Circles::AccountsController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:circle) { Fabricate(:circle, account: user.account) }
|
||||
let(:follow) { Fabricate(:follow, target_account: user.account) }
|
||||
|
||||
before do
|
||||
circle.accounts << follow.account
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
let(:scopes) { 'read:lists' }
|
||||
|
||||
it 'returns http success' do
|
||||
get :show, params: { circle_id: circle.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:scopes) { 'write:lists' }
|
||||
let(:bob) { Fabricate(:account, username: 'bob') }
|
||||
|
||||
context 'when the added account is followed' do
|
||||
before do
|
||||
bob.follow!(user.account)
|
||||
post :create, params: { circle_id: circle.id, account_ids: [bob.id] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'adds account to the circle' do
|
||||
expect(circle.accounts.include?(bob)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the added account has been sent a follow request' do
|
||||
before do
|
||||
bob.follow_requests.create!(target_account: user.account)
|
||||
post :create, params: { circle_id: circle.id, account_ids: [bob.id] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
|
||||
it 'adds account to the circle' do
|
||||
expect(circle.accounts.include?(bob)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the added account is not followed' do
|
||||
before do
|
||||
post :create, params: { circle_id: circle.id, account_ids: [bob.id] }
|
||||
end
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
|
||||
it 'does not add the account to the circle' do
|
||||
expect(circle.accounts.include?(bob)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
let(:scopes) { 'write:lists' }
|
||||
|
||||
before do
|
||||
delete :destroy, params: { circle_id: circle.id, account_ids: [circle.accounts.first.id] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'removes account from the circle' do
|
||||
expect(circle.accounts.count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
50
spec/controllers/api/v1/circles_controller_spec.rb
Normal file
50
spec/controllers/api/v1/circles_controller_spec.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::CirclesController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:circle) { Fabricate(:circle, account: user.account) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
context 'with a user context' do
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http success' do
|
||||
get :show, params: { id: circle.id }
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the wrong user context' do
|
||||
let(:other_user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http not found' do
|
||||
get :show, params: { id: circle.id }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a user context' do
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http unprocessable entity' do
|
||||
get :show, params: { id: circle.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.headers['Link']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
50
spec/controllers/api/v1/lists_controller_spec.rb
Normal file
50
spec/controllers/api/v1/lists_controller_spec.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::ListsController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:list) { Fabricate(:list, account: user.account) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
context 'with a user context' do
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http success' do
|
||||
get :show, params: { id: list.id }
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the wrong user context' do
|
||||
let(:other_user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http not found' do
|
||||
get :show, params: { id: list.id }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a user context' do
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') }
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns http unprocessable entity' do
|
||||
get :show, params: { id: list.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.headers['Link']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -66,5 +66,47 @@ describe TagFeed, type: :service do
|
|||
results = described_class.new(tag_cats, nil).get(20)
|
||||
expect(results).to include(status)
|
||||
end
|
||||
|
||||
it 'public_unlisted post returns' do
|
||||
status_tagged_with_cats.update(visibility: :public_unlisted)
|
||||
results = described_class.new(tag_cats, nil).get(20)
|
||||
expect(results).to include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'unlisted post not returns' do
|
||||
status_tagged_with_cats.update(visibility: :unlisted)
|
||||
results = described_class.new(tag_cats, nil).get(20)
|
||||
expect(results).to_not include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'unlisted post not returns with account' do
|
||||
status_tagged_with_cats.update(visibility: :unlisted)
|
||||
results = described_class.new(tag_cats, account).get(20)
|
||||
expect(results).to_not include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'unlisted/public_searchability post returns' do
|
||||
status_tagged_with_cats.update(visibility: :unlisted, searchability: :public)
|
||||
results = described_class.new(tag_cats, nil).get(20)
|
||||
expect(results).to include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'unlisted/public_searchability post returns with account' do
|
||||
status_tagged_with_cats.update(visibility: :unlisted, searchability: :public)
|
||||
results = described_class.new(tag_cats, account).get(20)
|
||||
expect(results).to include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'private post not returns' do
|
||||
status_tagged_with_cats.update(visibility: :private, searchability: :public)
|
||||
results = described_class.new(tag_cats, nil).get(20)
|
||||
expect(results).to_not include status_tagged_with_cats
|
||||
end
|
||||
|
||||
it 'private post not returns with account' do
|
||||
status_tagged_with_cats.update(visibility: :private, searchability: :public)
|
||||
results = described_class.new(tag_cats, account).get(20)
|
||||
expect(results).to_not include status_tagged_with_cats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -266,8 +266,8 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
|||
end
|
||||
|
||||
context 'when multiple antennas insert list with keyword' do
|
||||
let!(:antenna) { antenna_with_keyword(bob, 'body', insert_feeds: true, list: list(bob).id) }
|
||||
let!(:empty_antenna) { antenna_with_keyword(tom, 'body', insert_feeds: true, list: list(tom).id) }
|
||||
let!(:antenna) { antenna_with_keyword(bob, 'body', insert_feeds: true, list: list(bob)) }
|
||||
let!(:empty_antenna) { antenna_with_keyword(tom, 'body', insert_feeds: true, list: list(tom)) }
|
||||
|
||||
it 'detecting antenna' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
|
|
|
@ -7,9 +7,10 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
|
||||
let(:last_active_at) { Time.now.utc }
|
||||
let(:searchability) { 'public' }
|
||||
let(:dissubscribable) { false }
|
||||
let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, text: 'Hello @bob #hoge') }
|
||||
|
||||
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { dissubscribable: dissubscribable }).account }
|
||||
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
|
||||
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||
let!(:ohagi) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||
|
@ -100,6 +101,14 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is not added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with STL antenna' do
|
||||
|
@ -110,6 +119,14 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with LTL antenna' do
|
||||
|
@ -120,6 +137,14 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -238,6 +263,89 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when status is public_unlisted' do
|
||||
let(:visibility) { 'public_unlisted' }
|
||||
|
||||
it 'is added to the home feed of its author' do
|
||||
expect(home_feed_of(alice)).to include status.id
|
||||
end
|
||||
|
||||
it 'is added to the home feed of a follower' do
|
||||
expect(home_feed_of(bob)).to include status.id
|
||||
expect(home_feed_of(tom)).to include status.id
|
||||
end
|
||||
|
||||
it 'is broadcast publicly' do
|
||||
expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(redis).to have_received(:publish).with('timeline:public:local', anything)
|
||||
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||
end
|
||||
|
||||
context 'with list' do
|
||||
let!(:list) { list_with_account(bob, alice) }
|
||||
let!(:empty_list) { list_with_account(ohagi, bob) }
|
||||
|
||||
it 'is added to the list feed of list follower' do
|
||||
expect(list_feed_of(list)).to include status.id
|
||||
expect(list_feed_of(empty_list)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with antenna' do
|
||||
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||
let!(:empty_antenna) { antenna_with_account(tom, bob) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is not added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with STL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with LTL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, ltl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
|
||||
context 'when dissubscribable is true' do
|
||||
let(:dissubscribable) { true }
|
||||
|
||||
it 'is added to the antenna feed' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is unlisted' do
|
||||
let(:visibility) { 'unlisted' }
|
||||
|
||||
|
@ -255,6 +363,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||
end
|
||||
|
||||
context 'with searchability private' do
|
||||
let(:searchability) { 'private' }
|
||||
|
||||
it 'is not broadcast to the hashtag stream' do
|
||||
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with list' do
|
||||
let!(:list) { list_with_account(bob, alice) }
|
||||
let!(:empty_list) { list_with_account(ohagi, bob) }
|
||||
|
|
69
yarn.lock
69
yarn.lock
|
@ -1098,7 +1098,7 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
|
||||
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
|
||||
|
@ -1458,6 +1458,19 @@
|
|||
resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-2.1.1.tgz#648e63fc28050917d2882cc2b27817a88cb420fc"
|
||||
integrity sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==
|
||||
|
||||
"@hello-pangea/dnd@^16.3.0":
|
||||
version "16.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6"
|
||||
integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.22.5"
|
||||
css-box-model "^1.2.1"
|
||||
memoize-one "^6.0.0"
|
||||
raf-schd "^4.0.3"
|
||||
react-redux "^8.1.1"
|
||||
redux "^4.2.1"
|
||||
use-memo-one "^1.1.3"
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.11":
|
||||
version "0.11.11"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
|
||||
|
@ -2190,7 +2203,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
|
@ -2403,16 +2416,6 @@
|
|||
dependencies:
|
||||
react-overlays "*"
|
||||
|
||||
"@types/react-redux@^7.1.20":
|
||||
version "7.1.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
|
||||
integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
|
||||
|
@ -4456,7 +4459,7 @@ crypto-random-string@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
css-box-model@^1.2.0:
|
||||
css-box-model@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||
|
@ -8368,11 +8371,6 @@ media-typer@0.3.0:
|
|||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
|
||||
|
||||
memoize-one@^5.1.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
memoize-one@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||
|
@ -9924,7 +9922,7 @@ quick-lru@^5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
raf-schd@^4.0.2:
|
||||
raf-schd@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||
|
@ -9966,19 +9964,6 @@ raw-body@2.5.1:
|
|||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react-beautiful-dnd@^13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
||||
integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
css-box-model "^1.2.0"
|
||||
memoize-one "^5.1.1"
|
||||
raf-schd "^4.0.2"
|
||||
react-redux "^7.2.0"
|
||||
redux "^4.0.4"
|
||||
use-memo-one "^1.1.1"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
|
@ -10060,7 +10045,7 @@ react-is@^16.13.1, react-is@^16.7.0:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.1, react-is@^17.0.2:
|
||||
react-is@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
@ -10108,19 +10093,7 @@ react-redux-loading-bar@^5.0.4:
|
|||
prop-types "^15.7.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.9"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
|
||||
integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/react-redux" "^7.1.20"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-redux@^8.0.4:
|
||||
react-redux@^8.0.4, react-redux@^8.1.1:
|
||||
version "8.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188"
|
||||
integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==
|
||||
|
@ -10378,7 +10351,7 @@ redux-thunk@^2.4.2:
|
|||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
|
||||
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
|
||||
|
||||
redux@^4.0.0, redux@^4.0.4, redux@^4.2.1:
|
||||
redux@^4.0.0, redux@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||
|
@ -12312,7 +12285,7 @@ use-latest@^1.2.1:
|
|||
dependencies:
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
use-memo-one@^1.1.1:
|
||||
use-memo-one@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
|
||||
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue