diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb
index 4071c34799..0bacd7fdb0 100644
--- a/app/controllers/api/v1/lists_controller.rb
+++ b/app/controllers/api/v1/lists_controller.rb
@@ -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
diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js
index e5c606f4ec..8d1bff209e 100644
--- a/app/javascript/mastodon/actions/lists.js
+++ b/app/javascript/mastodon/actions/lists.js
@@ -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) {
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index e80131f979..39343d4796 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -148,6 +148,7 @@ const excludeTypesFromFilter = filter => {
'mention',
'poll',
'status',
+ 'list_status',
'update',
'admin.sign_up',
'admin.report',
diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx
index bebbe1574b..c4a5f155c7 100644
--- a/app/javascript/mastodon/features/list_timeline/index.jsx
+++ b/app/javascript/mastodon/features/list_timeline/index.jsx
@@ -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 {
+
diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx
index 8bb31b3956..2a472d35c5 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notification.jsx
@@ -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 (
+
+
+
+ );
+ }
+
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 =
{list.get('title')};
+ return this.renderListStatus(notification, listLink, link);
case 'update':
return this.renderUpdate(notification, link);
case 'poll':
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 8fbc847d1a..5a4a508b25 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -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",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 1477974d3a..e6f3cb557b 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -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": "アンケートが終了しました",
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 8ba9cf3c18..531645a5dd 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -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,
});
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 5cdba8008f..fcb75bfb74 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -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,
diff --git a/app/lib/vacuum/list_statuses_vacuum.rb b/app/lib/vacuum/list_statuses_vacuum.rb
new file mode 100644
index 0000000000..41dca7602f
--- /dev/null
+++ b/app/lib/vacuum/list_statuses_vacuum.rb
@@ -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
diff --git a/app/models/list.rb b/app/models/list.rb
index cf8f47963a..5d349a731a 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -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
diff --git a/app/models/list_status.rb b/app/models/list_status.rb
new file mode 100644
index 0000000000..a51e4fc331
--- /dev/null
+++ b/app/models/list_status.rb
@@ -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
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 50e45f05b3..ba63833e22 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -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
diff --git a/app/models/status.rb b/app/models/status.rb
index cdfd7b9555..5e6df5972f 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -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? }
diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb
index c6e420fca2..2415fe7283 100644
--- a/app/serializers/rest/list_serializer.rb
+++ b/app/serializers/rest/list_serializer.rb
@@ -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
diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb
index 71b5d69fc5..f0a3d96eb6 100644
--- a/app/serializers/rest/notification_serializer.rb
+++ b/app/serializers/rest/notification_serializer.rb
@@ -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
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index eaa5ce233b..9968d3d616 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -10,6 +10,8 @@ class NotifyService < BaseService
poll
emoji_reaction
status_reference
+ status
+ list_status
warning
).freeze
diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb
index 22acd0aa6f..c0a1722c10 100644
--- a/app/workers/feed_insert_worker.rb
+++ b/app/workers/feed_insert_worker.rb
@@ -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
diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb
index 1c9a2aabe3..eb4d2f5b10 100644
--- a/app/workers/scheduler/vacuum_scheduler.rb
+++ b/app/workers/scheduler/vacuum_scheduler.rb
@@ -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
diff --git a/db/migrate/20231028004612_create_list_statuses.rb b/db/migrate/20231028004612_create_list_statuses.rb
new file mode 100644
index 0000000000..30cd2da9df
--- /dev/null
+++ b/db/migrate/20231028004612_create_list_statuses.rb
@@ -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
diff --git a/db/migrate/20231028005948_add_notify_to_list.rb b/db/migrate/20231028005948_add_notify_to_list.rb
new file mode 100644
index 0000000000..df8bd473da
--- /dev/null
+++ b/db/migrate/20231028005948_add_notify_to_list.rb
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index e43428edf8..6bd862ac02 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.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
diff --git a/spec/fabricators/list_status_fabricator.rb b/spec/fabricators/list_status_fabricator.rb
new file mode 100644
index 0000000000..6324d92558
--- /dev/null
+++ b/spec/fabricators/list_status_fabricator.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+Fabricator(:list_status) do
+ list { Fabricate.build(:list) }
+ status
+end
diff --git a/spec/lib/vacuum/list_statuses_vacuum_spec.rb b/spec/lib/vacuum/list_statuses_vacuum_spec.rb
new file mode 100644
index 0000000000..d28b1ad8d9
--- /dev/null
+++ b/spec/lib/vacuum/list_statuses_vacuum_spec.rb
@@ -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
diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb
index a2466060c0..d515dd48c9 100644
--- a/spec/requests/api/v1/lists_spec.rb
+++ b/spec/requests/api/v1/lists_spec.rb
@@ -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
diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb
index 97c73c5999..c172dc4c1e 100644
--- a/spec/workers/feed_insert_worker_spec.rb
+++ b/spec/workers/feed_insert_worker_spec.rb
@@ -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