* Add: テーブル定義、内部処理 * Add: 通知の定期削除処理、自動削除、テスト * Add: Web画面の表示、設定 * Fix test
This commit is contained in:
parent
2cc60253c4
commit
f8280ca5d9
27 changed files with 300 additions and 9 deletions
|
@ -45,6 +45,6 @@ class Api::V1::ListsController < Api::BaseController
|
|||
end
|
||||
|
||||
def list_params
|
||||
params.permit(:title, :replies_policy, :exclusive)
|
||||
params.permit(:title, :replies_policy, :exclusive, :notify)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -151,10 +151,15 @@ export const createListFail = error => ({
|
|||
error,
|
||||
});
|
||||
|
||||
export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
|
||||
export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch, getState) => {
|
||||
dispatch(updateListRequest(id));
|
||||
|
||||
api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
|
||||
api(getState).put(`/api/v1/lists/${id}`, {
|
||||
title,
|
||||
replies_policy,
|
||||
exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive,
|
||||
notify: typeof notify === 'undefined' ? undefined : !!notify,
|
||||
}).then(({ data }) => {
|
||||
dispatch(updateListSuccess(data));
|
||||
|
||||
if (shouldReset) {
|
||||
|
|
|
@ -148,6 +148,7 @@ const excludeTypesFromFilter = filter => {
|
|||
'mention',
|
||||
'poll',
|
||||
'status',
|
||||
'list_status',
|
||||
'update',
|
||||
'admin.sign_up',
|
||||
'admin.report',
|
||||
|
|
|
@ -154,13 +154,19 @@ class ListTimeline extends PureComponent {
|
|||
handleRepliesPolicyChange = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateList(id, undefined, false, undefined, target.value));
|
||||
dispatch(updateList(id, undefined, false, undefined, target.value, undefined));
|
||||
};
|
||||
|
||||
onExclusiveToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateList(id, undefined, false, target.checked, undefined));
|
||||
dispatch(updateList(id, undefined, false, target.checked, undefined, undefined));
|
||||
};
|
||||
|
||||
onNotifyToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateList(id, undefined, false, undefined, undefined, target.checked));
|
||||
};
|
||||
|
||||
render () {
|
||||
|
@ -170,6 +176,7 @@ class ListTimeline extends PureComponent {
|
|||
const title = list ? list.get('title') : id;
|
||||
const replies_policy = list ? list.get('replies_policy') : undefined;
|
||||
const isExclusive = list ? list.get('exclusive') : undefined;
|
||||
const isNotify = list ? list.get('notify') : undefined;
|
||||
const antennas = list ? (list.get('antennas')?.toArray() || []) : [];
|
||||
|
||||
if (typeof list === 'undefined') {
|
||||
|
@ -216,6 +223,13 @@ class ListTimeline extends PureComponent {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isNotify} onChange={this.onNotifyToggle} />
|
||||
<label htmlFor={`list-${id}-notify`} className='setting-toggle__label'>
|
||||
<FormattedMessage id='lists.notify' defaultMessage='Notify these posts' />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{ replies_policy !== undefined && (
|
||||
<div role='group' aria-labelledby={`list-${id}-replies-policy`}>
|
||||
<span id={`list-${id}-replies-policy`} className='column-settings__section'>
|
||||
|
|
|
@ -190,6 +190,17 @@ export default class ColumnSettings extends PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-list_status'>
|
||||
<span id='notifications-list_status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.list_status' defaultMessage='New posts of list:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'list_status']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'list_status']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'list_status']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'list_status']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-update'>
|
||||
<span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { ReactComponent as FlagIcon } from '@material-symbols/svg-600/outlined/f
|
|||
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
|
||||
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
|
||||
import { ReactComponent as ReferenceIcon } from '@material-symbols/svg-600/outlined/link.svg';
|
||||
import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg';
|
||||
import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person-fill.svg';
|
||||
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg';
|
||||
import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
|
||||
|
@ -38,6 +39,7 @@ const messages = defineMessages({
|
|||
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
|
||||
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
|
||||
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
|
||||
listStatus: { id: 'notification.list_status', defaultMessage: '{name} post is added on {listName}' },
|
||||
statusReference: { id: 'notification.status_reference', defaultMessage: '{name} refered' },
|
||||
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
||||
warning: { id: 'notification.warning', defaultMessage: 'You have been warned and "{action}" has been executed. Check your mailbox' },
|
||||
|
@ -358,6 +360,42 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderListStatus (notification, listLink, link) {
|
||||
const { intl, unread, status } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-list_status focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.listStatus, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='list-ul' icon={ListAltIcon} />
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.list_status' defaultMessage='{name} post is added to {listName}' values={{ listName: listLink, name: link }} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
contextType='notifications'
|
||||
muted
|
||||
withDismiss
|
||||
hidden={this.props.hidden}
|
||||
getScrollPosition={this.props.getScrollPosition}
|
||||
updateScrollBottom={this.props.updateScrollBottom}
|
||||
cachedMediaWidth={this.props.cachedMediaWidth}
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
withoutEmojiReactions
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
renderUpdate (notification, link) {
|
||||
const { intl, unread, status } = this.props;
|
||||
|
||||
|
@ -531,6 +569,10 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderStatusReference(notification, link);
|
||||
case 'status':
|
||||
return this.renderStatus(notification, link);
|
||||
case 'list_status':
|
||||
const list = notification.get('list');
|
||||
const listLink = <bdi><Link className='notification__display-name' href={`/lists/${list.get('id')}`} title={list.get('title')} to={`/lists/${list.get('id')}`}>{list.get('title')}</Link></bdi>;
|
||||
return this.renderListStatus(notification, listLink, link);
|
||||
case 'update':
|
||||
return this.renderUpdate(notification, link);
|
||||
case 'poll':
|
||||
|
|
|
@ -399,6 +399,7 @@
|
|||
"lists.exclusive": "Hide list or antenna account posts from home",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.notify": "Notify these posts",
|
||||
"lists.replies_policy.followed": "Any followed user",
|
||||
"lists.replies_policy.list": "Members of the list",
|
||||
"lists.replies_policy.none": "No one",
|
||||
|
@ -448,6 +449,7 @@
|
|||
"notification.favourite": "{name} favorited your post",
|
||||
"notification.follow": "{name} followed you",
|
||||
"notification.follow_request": "{name} has requested to follow you",
|
||||
"notification.list_status": "{name} post is added to {listName}",
|
||||
"notification.mention": "{name} mentioned you",
|
||||
"notification.own_poll": "Your poll has ended",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
|
|
|
@ -474,6 +474,7 @@
|
|||
"lists.exclusive": "ホームからリスト・アンテナに登録されたアカウントの投稿を非表示にする",
|
||||
"lists.new.create": "リストを作成",
|
||||
"lists.new.title_placeholder": "新規リスト名",
|
||||
"lists.notify": "これらの投稿を通知する",
|
||||
"lists.replies_policy.followed": "フォロー中のユーザー全員",
|
||||
"lists.replies_policy.list": "リストのメンバー",
|
||||
"lists.replies_policy.none": "表示しない",
|
||||
|
@ -525,6 +526,7 @@
|
|||
"notification.favourite": "{name}さんがお気に入りしました",
|
||||
"notification.follow": "{name}さんにフォローされました",
|
||||
"notification.follow_request": "{name}さんがあなたにフォローリクエストしました",
|
||||
"notification.list_status": "{name}さんの投稿が{listName}に追加されました",
|
||||
"notification.mention": "{name}さんがあなたに返信しました",
|
||||
"notification.own_poll": "アンケートが終了しました",
|
||||
"notification.poll": "アンケートが終了しました",
|
||||
|
|
|
@ -55,6 +55,7 @@ const notificationToMap = notification => ImmutableMap({
|
|||
created_at: notification.created_at,
|
||||
emoji_reaction: ImmutableMap(notification.emoji_reaction),
|
||||
status: notification.status ? notification.status.id : null,
|
||||
list: notification.list ? ImmutableMap(notification.list) : null,
|
||||
report: notification.report ? fromJS(notification.report) : null,
|
||||
account_warning: notification.account_warning ? ImmutableMap(notification.account_warning) : null,
|
||||
});
|
||||
|
|
|
@ -42,6 +42,7 @@ const initialState = ImmutableMap({
|
|||
mention: false,
|
||||
poll: false,
|
||||
status: false,
|
||||
list_status: false,
|
||||
update: false,
|
||||
emoji_reaction: false,
|
||||
status_reference: false,
|
||||
|
@ -66,6 +67,7 @@ const initialState = ImmutableMap({
|
|||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
list_status: true,
|
||||
update: true,
|
||||
emoji_reaction: true,
|
||||
status_reference: true,
|
||||
|
@ -81,6 +83,7 @@ const initialState = ImmutableMap({
|
|||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
list_status: true,
|
||||
update: true,
|
||||
emoji_reaction: true,
|
||||
status_reference: true,
|
||||
|
|
17
app/lib/vacuum/list_statuses_vacuum.rb
Normal file
17
app/lib/vacuum/list_statuses_vacuum.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Vacuum::ListStatusesVacuum
|
||||
include Redisable
|
||||
|
||||
LIST_STATUS_LIFE_DURATION = 1.day.freeze
|
||||
|
||||
def perform
|
||||
vacuum_list_statuses!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def vacuum_list_statuses!
|
||||
ListStatus.where('created_at < ?', LIST_STATUS_LIFE_DURATION.ago).in_batches.destroy_all
|
||||
end
|
||||
end
|
|
@ -11,6 +11,7 @@
|
|||
# updated_at :datetime not null
|
||||
# replies_policy :integer default("list"), not null
|
||||
# exclusive :boolean default(FALSE), not null
|
||||
# notify :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class List < ApplicationRecord
|
||||
|
@ -25,6 +26,8 @@ class List < ApplicationRecord
|
|||
has_many :list_accounts, inverse_of: :list, dependent: :destroy
|
||||
has_many :accounts, through: :list_accounts
|
||||
has_many :antennas, inverse_of: :list, dependent: :destroy
|
||||
has_many :list_statuses, inverse_of: :list, dependent: :destroy
|
||||
has_many :statuses, through: :list_statuses
|
||||
|
||||
validates :title, presence: true
|
||||
|
||||
|
|
21
app/models/list_status.rb
Normal file
21
app/models/list_status.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: list_statuses
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# list_id :bigint(8) not null
|
||||
# status_id :bigint(8) not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class ListStatus < ApplicationRecord
|
||||
belongs_to :list
|
||||
belongs_to :status
|
||||
|
||||
has_one :notification, as: :activity, dependent: :destroy
|
||||
|
||||
validates :status, uniqueness: { scope: :list }
|
||||
end
|
|
@ -22,6 +22,7 @@ class Notification < ApplicationRecord
|
|||
LEGACY_TYPE_CLASS_MAP = {
|
||||
'Mention' => :mention,
|
||||
'Status' => :reblog,
|
||||
'ListStatus' => :list_status,
|
||||
'Follow' => :follow,
|
||||
'FollowRequest' => :follow_request,
|
||||
'Favourite' => :favourite,
|
||||
|
@ -34,6 +35,7 @@ class Notification < ApplicationRecord
|
|||
TYPES = %i(
|
||||
mention
|
||||
status
|
||||
list_status
|
||||
reblog
|
||||
status_reference
|
||||
follow
|
||||
|
@ -50,6 +52,7 @@ class Notification < ApplicationRecord
|
|||
|
||||
TARGET_STATUS_INCLUDES_BY_TYPE = {
|
||||
status: :status,
|
||||
list_status: [list_status: :status],
|
||||
reblog: [status: :reblog],
|
||||
status_reference: [status_reference: :status],
|
||||
mention: [mention: :status],
|
||||
|
@ -68,6 +71,7 @@ class Notification < ApplicationRecord
|
|||
with_options foreign_key: 'activity_id', optional: true do
|
||||
belongs_to :mention, inverse_of: :notification
|
||||
belongs_to :status, inverse_of: :notification
|
||||
belongs_to :list_status, inverse_of: :notification
|
||||
belongs_to :follow, inverse_of: :notification
|
||||
belongs_to :follow_request, inverse_of: :notification
|
||||
belongs_to :favourite, inverse_of: :notification
|
||||
|
@ -90,6 +94,8 @@ class Notification < ApplicationRecord
|
|||
case type
|
||||
when :status, :update
|
||||
status
|
||||
when :list_status
|
||||
list_status&.status
|
||||
when :reblog
|
||||
status&.reblog
|
||||
when :status_reference
|
||||
|
@ -143,6 +149,8 @@ class Notification < ApplicationRecord
|
|||
case notification.type
|
||||
when :status, :update
|
||||
notification.status = cached_status
|
||||
when :list_status
|
||||
notification.list_status.status = cached_status
|
||||
when :reblog
|
||||
notification.status.reblog = cached_status
|
||||
when :status_reference
|
||||
|
@ -182,7 +190,7 @@ class Notification < ApplicationRecord
|
|||
case activity_type
|
||||
when 'Status', 'Follow', 'Favourite', 'EmojiReaction', 'EmojiReact', 'FollowRequest', 'Poll', 'Report', 'AccountWarning'
|
||||
self.from_account_id = activity&.account_id
|
||||
when 'Mention', 'StatusReference'
|
||||
when 'Mention', 'StatusReference', 'ListStatus'
|
||||
self.from_account_id = activity&.status&.account_id
|
||||
when 'Account'
|
||||
self.from_account_id = activity&.id
|
||||
|
|
|
@ -111,6 +111,7 @@ class Status < ApplicationRecord
|
|||
has_one :trend, class_name: 'StatusTrend', inverse_of: :status
|
||||
has_one :scheduled_expiration_status, inverse_of: :status, dependent: :destroy
|
||||
has_one :circle_status, inverse_of: :status, dependent: :destroy
|
||||
has_many :list_status, inverse_of: :status, dependent: :destroy
|
||||
|
||||
validates :uri, uniqueness: true, presence: true, unless: :local?
|
||||
validates :text, presence: true, unless: -> { with_media? || reblog? }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title, :replies_policy, :exclusive
|
||||
attributes :id, :title, :replies_policy, :exclusive, :notify
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
|
|
@ -8,13 +8,14 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||
belongs_to :emoji_reaction, if: :emoji_reaction_type?, serializer: REST::NotifyEmojiReactionSerializer
|
||||
belongs_to :account_warning, if: :warning_type?, serializer: REST::AccountWarningSerializer
|
||||
belongs_to :list, if: :list_status_type?, serializer: REST::ListSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def status_type?
|
||||
[:favourite, :emoji_reaction, :reaction, :reblog, :status_reference, :status, :mention, :poll, :update].include?(object.type)
|
||||
[:favourite, :emoji_reaction, :reaction, :reblog, :status_reference, :status, :list_status, :mention, :poll, :update].include?(object.type)
|
||||
end
|
||||
|
||||
def report_type?
|
||||
|
@ -28,4 +29,12 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
def emoji_reaction_type?
|
||||
object.type == :emoji_reaction
|
||||
end
|
||||
|
||||
def list_status_type?
|
||||
object.type == :list_status
|
||||
end
|
||||
|
||||
def list
|
||||
object.list_status.list
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,8 @@ class NotifyService < BaseService
|
|||
poll
|
||||
emoji_reaction
|
||||
status_reference
|
||||
status
|
||||
list_status
|
||||
warning
|
||||
).freeze
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class FeedInsertWorker
|
|||
else
|
||||
perform_push
|
||||
perform_notify if notify?
|
||||
perform_notify_for_list if notify_for_list?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,6 +59,12 @@ class FeedInsertWorker
|
|||
Follow.find_by(account: @follower, target_account: @status.account)&.notify?
|
||||
end
|
||||
|
||||
def notify_for_list?
|
||||
return false unless @type == :list
|
||||
|
||||
@list.notify?
|
||||
end
|
||||
|
||||
def perform_push
|
||||
if @antenna.nil? || @antenna.insert_feeds
|
||||
case @type
|
||||
|
@ -90,6 +97,11 @@ class FeedInsertWorker
|
|||
LocalNotificationWorker.perform_async(@follower.id, @status.id, 'Status', 'status')
|
||||
end
|
||||
|
||||
def perform_notify_for_list
|
||||
list_status = ListStatus.create!(list: @list, status: @status)
|
||||
LocalNotificationWorker.perform_async(@list.account_id, list_status.id, 'ListStatus', 'list_status')
|
||||
end
|
||||
|
||||
def update?
|
||||
@options[:update]
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@ class Scheduler::VacuumScheduler
|
|||
applications_vacuum,
|
||||
feeds_vacuum,
|
||||
imports_vacuum,
|
||||
list_statuses_vacuum,
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -32,6 +33,10 @@ class Scheduler::VacuumScheduler
|
|||
Vacuum::StatusesVacuum.new(content_retention_policy.content_cache_retention_period)
|
||||
end
|
||||
|
||||
def list_statuses_vacuum
|
||||
Vacuum::ListStatusesVacuum.new
|
||||
end
|
||||
|
||||
def media_attachments_vacuum
|
||||
Vacuum::MediaAttachmentsVacuum.new(content_retention_policy.media_cache_retention_period)
|
||||
end
|
||||
|
|
22
db/migrate/20231028004612_create_list_statuses.rb
Normal file
22
db/migrate/20231028004612_create_list_statuses.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||
|
||||
class CreateListStatuses < ActiveRecord::Migration[7.1]
|
||||
include Mastodon::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
safety_assured do
|
||||
create_table :list_statuses do |t|
|
||||
t.belongs_to :list, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.belongs_to :status, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.datetime :created_at, null: false
|
||||
t.datetime :updated_at, null: false
|
||||
end
|
||||
|
||||
add_index :list_statuses, [:list_id, :status_id], unique: true
|
||||
end
|
||||
end
|
||||
end
|
15
db/migrate/20231028005948_add_notify_to_list.rb
Normal file
15
db/migrate/20231028005948_add_notify_to_list.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||
|
||||
class AddNotifyToList < ActiveRecord::Migration[7.0]
|
||||
include Mastodon::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
safety_assured do
|
||||
add_column_with_default :lists, :notify, :boolean, default: false, allow_null: false
|
||||
end
|
||||
end
|
||||
end
|
15
db/schema.rb
15
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2023_10_28_005948) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -761,6 +761,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do
|
|||
t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id"
|
||||
end
|
||||
|
||||
create_table "list_statuses", force: :cascade do |t|
|
||||
t.bigint "list_id", null: false
|
||||
t.bigint "status_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["list_id", "status_id"], name: "index_list_statuses_on_list_id_and_status_id", unique: true
|
||||
t.index ["list_id"], name: "index_list_statuses_on_list_id"
|
||||
t.index ["status_id"], name: "index_list_statuses_on_status_id"
|
||||
end
|
||||
|
||||
create_table "lists", force: :cascade do |t|
|
||||
t.bigint "account_id", null: false
|
||||
t.string "title", default: "", null: false
|
||||
|
@ -768,6 +778,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do
|
|||
t.datetime "updated_at", precision: nil, null: false
|
||||
t.integer "replies_policy", default: 0, null: false
|
||||
t.boolean "exclusive", default: false, null: false
|
||||
t.boolean "notify", default: false, null: false
|
||||
t.index ["account_id"], name: "index_lists_on_account_id"
|
||||
end
|
||||
|
||||
|
@ -1480,6 +1491,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do
|
|||
add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
|
||||
add_foreign_key "list_accounts", "follows", on_delete: :cascade
|
||||
add_foreign_key "list_accounts", "lists", on_delete: :cascade
|
||||
add_foreign_key "list_statuses", "lists", on_delete: :cascade
|
||||
add_foreign_key "list_statuses", "statuses", on_delete: :cascade
|
||||
add_foreign_key "lists", "accounts", on_delete: :cascade
|
||||
add_foreign_key "login_activities", "users", on_delete: :cascade
|
||||
add_foreign_key "markers", "users", on_delete: :cascade
|
||||
|
|
6
spec/fabricators/list_status_fabricator.rb
Normal file
6
spec/fabricators/list_status_fabricator.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:list_status) do
|
||||
list { Fabricate.build(:list) }
|
||||
status
|
||||
end
|
30
spec/lib/vacuum/list_statuses_vacuum_spec.rb
Normal file
30
spec/lib/vacuum/list_statuses_vacuum_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Vacuum::ListStatusesVacuum do
|
||||
subject { described_class.new }
|
||||
|
||||
describe '#perform' do
|
||||
let!(:local_status_old) { Fabricate(:status, created_at: 2.days.ago) }
|
||||
let!(:local_status_recent) { Fabricate(:status, created_at: 5.hours.ago) }
|
||||
let!(:list_status_old) { Fabricate(:list_status, status: local_status_old, created_at: local_status_old.created_at) }
|
||||
let!(:list_status_recent) { Fabricate(:list_status, status: local_status_recent, created_at: local_status_recent.created_at) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'deletes old list status' do
|
||||
expect { list_status_old.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
it 'does not delete recent status' do
|
||||
expect { list_status_recent.reload }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'statuses are remain' do
|
||||
expect { local_status_old }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ RSpec.describe 'Lists' do
|
|||
Fabricate(:list, account: user.account, title: 'second list', replies_policy: :list),
|
||||
Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
|
||||
Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
|
||||
Fabricate(:list, account: user.account, title: 'fourth list', notify: true),
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -30,6 +31,7 @@ RSpec.describe 'Lists' do
|
|||
replies_policy: list.replies_policy,
|
||||
exclusive: list.exclusive,
|
||||
antennas: list.antennas,
|
||||
notify: list.notify,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -67,6 +69,7 @@ RSpec.describe 'Lists' do
|
|||
replies_policy: list.replies_policy,
|
||||
exclusive: list.exclusive,
|
||||
antennas: list.antennas,
|
||||
notify: list.notify,
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -149,6 +152,7 @@ RSpec.describe 'Lists' do
|
|||
replies_policy: list.replies_policy,
|
||||
exclusive: list.exclusive,
|
||||
antennas: list.antennas,
|
||||
notify: list.notify,
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@ require 'rails_helper'
|
|||
describe FeedInsertWorker do
|
||||
subject { described_class.new }
|
||||
|
||||
def notify?(account, type, activity_id)
|
||||
Notification.exists?(account: account, type: type, activity_id: activity_id)
|
||||
end
|
||||
|
||||
describe 'perform' do
|
||||
let(:follower) { Fabricate(:account) }
|
||||
let(:status) { Fabricate(:status) }
|
||||
|
@ -48,5 +52,43 @@ describe FeedInsertWorker do
|
|||
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with notification' do
|
||||
it 'skips notification when unset' do
|
||||
subject.perform(status.id, follower.id)
|
||||
expect(notify?(follower, 'status', status.id)).to be false
|
||||
end
|
||||
|
||||
it 'pushes notification when read status is set' do
|
||||
Fabricate(:follow, account: follower, target_account: status.account, notify: true)
|
||||
|
||||
subject.perform(status.id, follower.id)
|
||||
expect(notify?(follower, 'status', status.id)).to be true
|
||||
end
|
||||
|
||||
it 'skips notification when the account is registered list but not notify' do
|
||||
follower.follow!(status.account)
|
||||
list = Fabricate(:list, account: follower)
|
||||
Fabricate(:list_account, list: list, account: status.account)
|
||||
|
||||
subject.perform(status.id, list.id, 'list')
|
||||
|
||||
list_status = ListStatus.find_by(list: list, status: status)
|
||||
|
||||
expect(list_status).to be_nil
|
||||
end
|
||||
|
||||
it 'pushes notification when the account is registered list' do
|
||||
follower.follow!(status.account)
|
||||
list = Fabricate(:list, account: follower, notify: true)
|
||||
Fabricate(:list_account, list: list, account: status.account)
|
||||
|
||||
subject.perform(status.id, list.id, 'list')
|
||||
list_status = ListStatus.find_by(list: list, status: status)
|
||||
|
||||
expect(list_status).to_not be_nil
|
||||
expect(notify?(follower, 'list_status', list_status.id)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue