Add added_to_collection and collection_updated notification types (#38491)
This commit is contained in:
parent
df64716b34
commit
39c70649ca
@ -3,6 +3,7 @@
|
|||||||
import type { AccountWarningAction } from 'mastodon/models/notification_group';
|
import type { AccountWarningAction } from 'mastodon/models/notification_group';
|
||||||
|
|
||||||
import type { ApiAccountJSON } from './accounts';
|
import type { ApiAccountJSON } from './accounts';
|
||||||
|
import type { ApiCollectionJSON } from './collections';
|
||||||
import type { ApiReportJSON } from './reports';
|
import type { ApiReportJSON } from './reports';
|
||||||
import type { ApiStatusJSON } from './statuses';
|
import type { ApiStatusJSON } from './statuses';
|
||||||
|
|
||||||
@ -22,6 +23,8 @@ export const allNotificationTypes: NotificationType[] = [
|
|||||||
'moderation_warning',
|
'moderation_warning',
|
||||||
'severed_relationships',
|
'severed_relationships',
|
||||||
'annual_report',
|
'annual_report',
|
||||||
|
'added_to_collection',
|
||||||
|
'collection_update',
|
||||||
];
|
];
|
||||||
|
|
||||||
export type NotificationWithStatusType =
|
export type NotificationWithStatusType =
|
||||||
@ -42,7 +45,9 @@ export type NotificationType =
|
|||||||
| 'severed_relationships'
|
| 'severed_relationships'
|
||||||
| 'admin.sign_up'
|
| 'admin.sign_up'
|
||||||
| 'admin.report'
|
| 'admin.report'
|
||||||
| 'annual_report';
|
| 'annual_report'
|
||||||
|
| 'added_to_collection'
|
||||||
|
| 'collection_update';
|
||||||
|
|
||||||
export interface BaseNotificationJSON {
|
export interface BaseNotificationJSON {
|
||||||
id: string;
|
id: string;
|
||||||
@ -83,6 +88,26 @@ interface ReportNotificationJSON extends BaseNotificationJSON {
|
|||||||
report: ApiReportJSON;
|
report: ApiReportJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AddedToCollectionNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||||
|
type: 'added_to_collection';
|
||||||
|
collection: ApiCollectionJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddedToCollectionNotificationJSON extends BaseNotificationJSON {
|
||||||
|
type: 'added_to_collection';
|
||||||
|
collection: ApiCollectionJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CollectionUpdateNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||||
|
type: 'collection_update';
|
||||||
|
collection: ApiCollectionJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CollectionUpdateNotificationJSON extends BaseNotificationJSON {
|
||||||
|
type: 'collection_update';
|
||||||
|
collection: ApiCollectionJSON;
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleNotificationTypes = 'follow' | 'follow_request' | 'admin.sign_up';
|
type SimpleNotificationTypes = 'follow' | 'follow_request' | 'admin.sign_up';
|
||||||
interface SimpleNotificationGroupJSON extends BaseNotificationGroupJSON {
|
interface SimpleNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||||
type: SimpleNotificationTypes;
|
type: SimpleNotificationTypes;
|
||||||
@ -146,7 +171,9 @@ export type ApiNotificationJSON =
|
|||||||
| ReportNotificationJSON
|
| ReportNotificationJSON
|
||||||
| AccountRelationshipSeveranceNotificationJSON
|
| AccountRelationshipSeveranceNotificationJSON
|
||||||
| NotificationWithStatusJSON
|
| NotificationWithStatusJSON
|
||||||
| ModerationWarningNotificationJSON;
|
| ModerationWarningNotificationJSON
|
||||||
|
| AddedToCollectionNotificationJSON
|
||||||
|
| CollectionUpdateNotificationJSON;
|
||||||
|
|
||||||
export type ApiNotificationGroupJSON =
|
export type ApiNotificationGroupJSON =
|
||||||
| SimpleNotificationGroupJSON
|
| SimpleNotificationGroupJSON
|
||||||
@ -154,7 +181,9 @@ export type ApiNotificationGroupJSON =
|
|||||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||||
| NotificationGroupWithStatusJSON
|
| NotificationGroupWithStatusJSON
|
||||||
| ModerationWarningNotificationGroupJSON
|
| ModerationWarningNotificationGroupJSON
|
||||||
| AnnualReportNotificationGroupJSON;
|
| AnnualReportNotificationGroupJSON
|
||||||
|
| AddedToCollectionNotificationGroupJSON
|
||||||
|
| CollectionUpdateNotificationGroupJSON;
|
||||||
|
|
||||||
export interface ApiNotificationGroupsResultJSON {
|
export interface ApiNotificationGroupsResultJSON {
|
||||||
accounts: ApiAccountJSON[];
|
accounts: ApiAccountJSON[];
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import type {
|
|||||||
} from 'mastodon/api_types/notifications';
|
} from 'mastodon/api_types/notifications';
|
||||||
import type { ApiReportJSON } from 'mastodon/api_types/reports';
|
import type { ApiReportJSON } from 'mastodon/api_types/reports';
|
||||||
|
|
||||||
|
import type { ApiCollectionJSON } from '../api_types/collections';
|
||||||
|
|
||||||
// Maximum number of avatars displayed in a notification group
|
// Maximum number of avatars displayed in a notification group
|
||||||
// This corresponds to the max length of `group.sampleAccountIds`
|
// This corresponds to the max length of `group.sampleAccountIds`
|
||||||
export const NOTIFICATIONS_GROUP_MAX_AVATARS = 8;
|
export const NOTIFICATIONS_GROUP_MAX_AVATARS = 8;
|
||||||
@ -87,6 +89,15 @@ export interface NotificationGroupAdminReport extends BaseNotification<'admin.re
|
|||||||
report: Report;
|
report: Report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Collection = ApiCollectionJSON;
|
||||||
|
export interface NotificationGroupAddedToCollection extends BaseNotification<'added_to_collection'> {
|
||||||
|
collection: Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationGroupCollectionUpdate extends BaseNotification<'collection_update'> {
|
||||||
|
collection: Collection;
|
||||||
|
}
|
||||||
|
|
||||||
export type NotificationGroup =
|
export type NotificationGroup =
|
||||||
| NotificationGroupFavourite
|
| NotificationGroupFavourite
|
||||||
| NotificationGroupReblog
|
| NotificationGroupReblog
|
||||||
@ -102,7 +113,9 @@ export type NotificationGroup =
|
|||||||
| NotificationGroupSeveredRelationships
|
| NotificationGroupSeveredRelationships
|
||||||
| NotificationGroupAdminSignUp
|
| NotificationGroupAdminSignUp
|
||||||
| NotificationGroupAdminReport
|
| NotificationGroupAdminReport
|
||||||
| NotificationGroupAnnualReport;
|
| NotificationGroupAnnualReport
|
||||||
|
| NotificationGroupAddedToCollection
|
||||||
|
| NotificationGroupCollectionUpdate;
|
||||||
|
|
||||||
function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
||||||
const { target_account, ...report } = reportJSON;
|
const { target_account, ...report } = reportJSON;
|
||||||
@ -249,6 +262,13 @@ export function createNotificationGroupFromNotificationJSON(
|
|||||||
notification.moderation_warning,
|
notification.moderation_warning,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
case 'added_to_collection':
|
||||||
|
case 'collection_update':
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
type: notification.type,
|
||||||
|
collection: notification.collection,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
...group,
|
...group,
|
||||||
|
|||||||
@ -26,6 +26,7 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity
|
|||||||
collection_item_attributes(:accepted)
|
collection_item_attributes(:accepted)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
notify_local_user!(collection_item)
|
||||||
queue_delivery!(collection_item, ActivityPub::AcceptFeatureRequestSerializer)
|
queue_delivery!(collection_item, ActivityPub::AcceptFeatureRequestSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -52,6 +53,10 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity
|
|||||||
{ account: @featured_account, activity_uri: @json['id'], state: }
|
{ account: @featured_account, activity_uri: @json['id'], state: }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_local_user!(collection_item)
|
||||||
|
LocalNotificationWorker.perform_async(collection_item.account_id, collection_item.id, collection_item.class.name, 'added_to_collection')
|
||||||
|
end
|
||||||
|
|
||||||
def queue_delivery!(collection_item, serializer)
|
def queue_delivery!(collection_item, serializer)
|
||||||
json = JSON.generate(serialize_payload(collection_item, serializer))
|
json = JSON.generate(serialize_payload(collection_item, serializer))
|
||||||
ActivityPub::DeliveryWorker.perform_async(json, @featured_account.id, @account.inbox_url)
|
ActivityPub::DeliveryWorker.perform_async(json, @featured_account.id, @account.inbox_url)
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class Notification < ApplicationRecord
|
|||||||
'Quote' => :quote,
|
'Quote' => :quote,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# Please update app/javascript/api_types/notification.ts if you change this
|
# Please update app/javascript/mastodon/api_types/notifications.ts if you change this
|
||||||
PROPERTIES = {
|
PROPERTIES = {
|
||||||
mention: {
|
mention: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
@ -80,6 +80,12 @@ class Notification < ApplicationRecord
|
|||||||
quoted_update: {
|
quoted_update: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
|
added_to_collection: {
|
||||||
|
filterable: true,
|
||||||
|
}.freeze,
|
||||||
|
collection_update: {
|
||||||
|
filterable: false,
|
||||||
|
},
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
TYPES = PROPERTIES.keys.freeze
|
TYPES = PROPERTIES.keys.freeze
|
||||||
@ -112,6 +118,8 @@ class Notification < ApplicationRecord
|
|||||||
belongs_to :account_warning, inverse_of: false
|
belongs_to :account_warning, inverse_of: false
|
||||||
belongs_to :generated_annual_report, inverse_of: false
|
belongs_to :generated_annual_report, inverse_of: false
|
||||||
belongs_to :quote, inverse_of: :notification
|
belongs_to :quote, inverse_of: :notification
|
||||||
|
belongs_to :collection_item, inverse_of: false # TODO: have an inverse?
|
||||||
|
belongs_to :collection, inverse_of: false # TODO: have an inverse?
|
||||||
end
|
end
|
||||||
|
|
||||||
validates :type, inclusion: { in: TYPES }
|
validates :type, inclusion: { in: TYPES }
|
||||||
@ -208,8 +216,10 @@ class Notification < ApplicationRecord
|
|||||||
case activity_type
|
case activity_type
|
||||||
when 'Status'
|
when 'Status'
|
||||||
self.from_account_id = type == :quoted_update ? activity&.quote&.quoted_account_id : activity&.account_id
|
self.from_account_id = type == :quoted_update ? activity&.quote&.quoted_account_id : activity&.account_id
|
||||||
when 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'Quote'
|
when 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'Quote', 'Collection'
|
||||||
self.from_account_id = activity&.account_id
|
self.from_account_id = activity&.account_id
|
||||||
|
when 'CollectionItem'
|
||||||
|
self.from_account_id = activity&.collection&.account_id
|
||||||
when 'Mention'
|
when 'Mention'
|
||||||
self.from_account_id = activity&.status&.account_id
|
self.from_account_id = activity&.status&.account_id
|
||||||
when 'Account'
|
when 'Account'
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
||||||
# Please update app/javascript/api_types/notification.ts when making changes to the attributes
|
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
||||||
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
|
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
|
||||||
|
|
||||||
attribute :page_min_id, if: :paginated?
|
attribute :page_min_id, if: :paginated?
|
||||||
@ -14,6 +14,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||||||
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
||||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
||||||
belongs_to :generated_annual_report, key: :annual_report, if: :annual_report_event?, serializer: REST::AnnualReportEventSerializer
|
belongs_to :generated_annual_report, key: :annual_report, if: :annual_report_event?, serializer: REST::AnnualReportEventSerializer
|
||||||
|
belongs_to :collection, if: :collection_type?, serializer: REST::CollectionSerializer
|
||||||
|
|
||||||
def sample_account_ids
|
def sample_account_ids
|
||||||
object.sample_accounts.pluck(:id).map(&:to_s)
|
object.sample_accounts.pluck(:id).map(&:to_s)
|
||||||
@ -27,6 +28,10 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||||||
[:favourite, :reblog, :status, :mention, :poll, :update, :quote, :quoted_update].include?(object.type)
|
[:favourite, :reblog, :status, :mention, :poll, :update, :quote, :quoted_update].include?(object.type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def collection_type?
|
||||||
|
[:added_to_collection, :collection_update].include?(object.type)
|
||||||
|
end
|
||||||
|
|
||||||
def report_type?
|
def report_type?
|
||||||
object.type == :'admin.report'
|
object.type == :'admin.report'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::NotificationSerializer < ActiveModel::Serializer
|
class REST::NotificationSerializer < ActiveModel::Serializer
|
||||||
# Please update app/javascript/api_types/notification.ts when making changes to the attributes
|
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
||||||
attributes :id, :type, :created_at, :group_key
|
attributes :id, :type, :created_at, :group_key
|
||||||
|
|
||||||
attribute :filtered, if: :filtered?
|
attribute :filtered, if: :filtered?
|
||||||
@ -11,6 +11,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||||
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
||||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
||||||
|
belongs_to :collection, if: :collection_type?, serializer: REST::CollectionSerializer
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
@ -24,6 +25,10 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||||||
[:favourite, :reblog, :status, :mention, :poll, :update, :quoted_update, :quote].include?(object.type)
|
[:favourite, :reblog, :status, :mention, :poll, :update, :quoted_update, :quote].include?(object.type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def collection_type?
|
||||||
|
[:added_to_collection, :collection_update].include?(object.type)
|
||||||
|
end
|
||||||
|
|
||||||
def report_type?
|
def report_type?
|
||||||
object.type == :'admin.report'
|
object.type == :'admin.report'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class ActivityPub::ProcessFeaturedCollectionService
|
|||||||
end
|
end
|
||||||
|
|
||||||
process_items!
|
process_items!
|
||||||
|
notify_about_update!
|
||||||
|
|
||||||
@collection
|
@collection
|
||||||
end
|
end
|
||||||
@ -32,6 +33,10 @@ class ActivityPub::ProcessFeaturedCollectionService
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def notify_about_update!
|
||||||
|
NotifyOfCollectionUpdateService.new.call(@collection)
|
||||||
|
end
|
||||||
|
|
||||||
def truncated_summary
|
def truncated_summary
|
||||||
text = @json['summaryMap']&.values&.first || @json['summary'] || ''
|
text = @json['summaryMap']&.values&.first || @json['summary'] || ''
|
||||||
text[0, Collection::DESCRIPTION_LENGTH_HARD_LIMIT]
|
text[0, Collection::DESCRIPTION_LENGTH_HARD_LIMIT]
|
||||||
|
|||||||
@ -11,6 +11,7 @@ class AddAccountToCollectionService
|
|||||||
|
|
||||||
@collection_item = create_collection_item
|
@collection_item = create_collection_item
|
||||||
|
|
||||||
|
notify_local_user if @account.local?
|
||||||
distribute_add_activity if @account.local?
|
distribute_add_activity if @account.local?
|
||||||
distribute_feature_request_activity if @account.remote?
|
distribute_feature_request_activity if @account.remote?
|
||||||
|
|
||||||
@ -24,6 +25,10 @@ class AddAccountToCollectionService
|
|||||||
@collection.collection_items.create!(account: @account, state:)
|
@collection.collection_items.create!(account: @account, state:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_local_user
|
||||||
|
LocalNotificationWorker.perform_async(@account.id, @collection_item.id, @collection_item.class.name, 'added_to_collection')
|
||||||
|
end
|
||||||
|
|
||||||
def distribute_add_activity
|
def distribute_add_activity
|
||||||
ActivityPub::AccountRawDistributionWorker.perform_async(add_activity_json, @collection.account_id)
|
ActivityPub::AccountRawDistributionWorker.perform_async(add_activity_json, @collection.account_id)
|
||||||
end
|
end
|
||||||
|
|||||||
21
app/services/notify_of_collection_update_service.rb
Normal file
21
app/services/notify_of_collection_update_service.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class NotifyOfCollectionUpdateService
|
||||||
|
def call(collection)
|
||||||
|
return unless significantly_changed?(collection)
|
||||||
|
|
||||||
|
collection.collection_items.includes(:account).references(:account).merge(Account.local).accepted.find_each do |collection_item|
|
||||||
|
LocalNotificationWorker.perform_async(collection_item.account_id, collection.id, collection.class.name, 'collection_update')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def significantly_changed?(collection)
|
||||||
|
# If the collection is brand new, we don't need to look at its members
|
||||||
|
return false if collection.previously_new_record?
|
||||||
|
|
||||||
|
# Only notify of change to description or name
|
||||||
|
%i(description description_html name).any? { |attr| collection.attribute_previously_changed?(attr) }
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -14,6 +14,8 @@ class NotifyService < BaseService
|
|||||||
moderation_warning
|
moderation_warning
|
||||||
severed_relationships
|
severed_relationships
|
||||||
annual_report
|
annual_report
|
||||||
|
added_to_collection
|
||||||
|
collection_update
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
class BaseCondition
|
class BaseCondition
|
||||||
@ -21,15 +23,6 @@ class NotifyService < BaseService
|
|||||||
|
|
||||||
NEW_FOLLOWER_THRESHOLD = 3.days.freeze
|
NEW_FOLLOWER_THRESHOLD = 3.days.freeze
|
||||||
|
|
||||||
NON_FILTERABLE_TYPES = %i(
|
|
||||||
admin.sign_up
|
|
||||||
admin.report
|
|
||||||
poll
|
|
||||||
update
|
|
||||||
account_warning
|
|
||||||
annual_report
|
|
||||||
).freeze
|
|
||||||
|
|
||||||
def initialize(notification, **options)
|
def initialize(notification, **options)
|
||||||
@recipient = notification.account
|
@recipient = notification.account
|
||||||
@sender = notification.from_account
|
@sender = notification.from_account
|
||||||
|
|||||||
@ -7,6 +7,7 @@ class UpdateCollectionService
|
|||||||
@collection = collection
|
@collection = collection
|
||||||
@collection.update!(params)
|
@collection.update!(params)
|
||||||
|
|
||||||
|
notify_about_update
|
||||||
distribute_update_activity
|
distribute_update_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -18,6 +19,10 @@ class UpdateCollectionService
|
|||||||
ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @collection.account.id)
|
ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @collection.account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_about_update
|
||||||
|
NotifyOfCollectionUpdateService.new.call(@collection)
|
||||||
|
end
|
||||||
|
|
||||||
def activity_json
|
def activity_json
|
||||||
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::UpdateFeaturedCollectionSerializer, adapter: ActivityPub::Adapter).to_json
|
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::UpdateFeaturedCollectionSerializer, adapter: ActivityPub::Adapter).to_json
|
||||||
end
|
end
|
||||||
|
|||||||
@ -10,10 +10,8 @@ class LocalNotificationWorker
|
|||||||
# For most notification types, only one notification should exist, and the older one is
|
# For most notification types, only one notification should exist, and the older one is
|
||||||
# preferred. For updates, such as when a status is edited, the new notification
|
# preferred. For updates, such as when a status is edited, the new notification
|
||||||
# should replace the previous ones.
|
# should replace the previous ones.
|
||||||
if type == 'update'
|
if %w(update quoted_update collection_update).include?(type)
|
||||||
Notification.where(account: receiver, activity: activity, type: 'update').in_batches.delete_all
|
Notification.where(account: receiver, activity: activity, type: type).in_batches.delete_all
|
||||||
elsif type == 'quoted_update'
|
|
||||||
Notification.where(account: receiver, activity: activity, type: 'quoted_update').in_batches.delete_all
|
|
||||||
elsif Notification.where(account: receiver, activity: activity, type: type).any?
|
elsif Notification.where(account: receiver, activity: activity, type: type).any?
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@ -26,9 +26,10 @@ RSpec.describe ActivityPub::Activity::FeatureRequest do
|
|||||||
context 'when recipient is discoverable' do
|
context 'when recipient is discoverable' do
|
||||||
let(:discoverable) { true }
|
let(:discoverable) { true }
|
||||||
|
|
||||||
it 'schedules a job to send an `Accept` activity' do
|
it 'schedules a job to send an `Accept` activity as well as a notification worker' do
|
||||||
expect { subject.perform }
|
expect { subject.perform }
|
||||||
.to enqueue_sidekiq_job(ActivityPub::DeliveryWorker)
|
.to enqueue_sidekiq_job(LocalNotificationWorker).with(recipient.id, anything, 'CollectionItem', 'added_to_collection')
|
||||||
|
.and enqueue_sidekiq_job(ActivityPub::DeliveryWorker)
|
||||||
.with(satisfying do |body|
|
.with(satisfying do |body|
|
||||||
response_json = JSON.parse(body)
|
response_json = JSON.parse(body)
|
||||||
response_json['type'] == 'Accept' &&
|
response_json['type'] == 'Accept' &&
|
||||||
|
|||||||
@ -259,6 +259,9 @@ RSpec.describe ActivityPub::Activity::Update do
|
|||||||
|
|
||||||
context 'with a `FeaturedCollection` object', feature: :collections do
|
context 'with a `FeaturedCollection` object', feature: :collections do
|
||||||
let(:collection) { Fabricate(:remote_collection, account: sender, name: 'old name', discoverable: false) }
|
let(:collection) { Fabricate(:remote_collection, account: sender, name: 'old name', discoverable: false) }
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let!(:collection_item) { Fabricate(:collection_item, account:, collection:, uri: 'https://example.com/featured_stamps/1') }
|
||||||
|
|
||||||
let(:featured_collection_json) do
|
let(:featured_collection_json) do
|
||||||
{
|
{
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
@ -267,13 +270,21 @@ RSpec.describe ActivityPub::Activity::Update do
|
|||||||
'attributedTo' => sender.uri,
|
'attributedTo' => sender.uri,
|
||||||
'name' => 'Cool people',
|
'name' => 'Cool people',
|
||||||
'summary' => 'People you should follow.',
|
'summary' => 'People you should follow.',
|
||||||
'totalItems' => 0,
|
'totalItems' => 1,
|
||||||
'sensitive' => false,
|
'sensitive' => false,
|
||||||
'discoverable' => true,
|
'discoverable' => true,
|
||||||
'published' => '2026-03-09T15:19:25Z',
|
'published' => '2026-03-09T15:19:25Z',
|
||||||
'updated' => Time.zone.now.iso8601,
|
'updated' => Time.zone.now.iso8601,
|
||||||
|
'orderedItems' => [
|
||||||
|
{
|
||||||
|
'type' => 'FeaturedItem',
|
||||||
|
'id' => ActivityPub::TagManager.instance.uri_for(collection_item),
|
||||||
|
'object' => ActivityPub::TagManager.instance.uri_for(account),
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:json) do
|
let(:json) do
|
||||||
{
|
{
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
@ -282,18 +293,41 @@ RSpec.describe ActivityPub::Activity::Update do
|
|||||||
'object' => featured_collection_json,
|
'object' => featured_collection_json,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:stubbed_service) do
|
|
||||||
instance_double(ActivityPub::ProcessFeaturedCollectionService, call: true)
|
it 'updates the collection and notifies local user' do
|
||||||
|
expect { subject.perform }
|
||||||
|
.to change { collection.reload.name }.to(featured_collection_json['name'])
|
||||||
|
.and enqueue_sidekiq_job(LocalNotificationWorker).with(account.id, collection.id, 'Collection', 'collection_update')
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
context 'when the metadata does not actually change' do
|
||||||
allow(ActivityPub::ProcessFeaturedCollectionService).to receive(:new).and_return(stubbed_service)
|
let(:featured_collection_json) do
|
||||||
end
|
{
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id' => collection.uri,
|
||||||
|
'type' => 'FeaturedCollection',
|
||||||
|
'attributedTo' => sender.uri,
|
||||||
|
'name' => collection.name,
|
||||||
|
'summary' => collection.description_html,
|
||||||
|
'totalItems' => 1,
|
||||||
|
'sensitive' => false,
|
||||||
|
'discoverable' => true,
|
||||||
|
'published' => '2026-03-09T15:19:25Z',
|
||||||
|
'updated' => Time.zone.now.iso8601,
|
||||||
|
'orderedItems' => [
|
||||||
|
{
|
||||||
|
'type' => 'FeaturedItem',
|
||||||
|
'id' => ActivityPub::TagManager.instance.uri_for(collection_item),
|
||||||
|
'object' => ActivityPub::TagManager.instance.uri_for(account),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
it 'updates the collection' do
|
it 'does not notify the local user' do
|
||||||
subject.perform
|
expect { subject.perform }
|
||||||
|
.to_not enqueue_sidekiq_job(LocalNotificationWorker)
|
||||||
expect(stubbed_service).to have_received(:call).with(sender, featured_collection_json)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -22,10 +22,15 @@ RSpec.describe AddAccountToCollectionService do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when the account is local' do
|
context 'when the account is local' do
|
||||||
it 'federates an `Add` activity' do
|
it 'federates an `Add` activity and schedules a notification' do
|
||||||
subject.call(collection, account)
|
subject.call(collection, account)
|
||||||
|
|
||||||
expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job
|
expect(ActivityPub::AccountRawDistributionWorker)
|
||||||
|
.to have_enqueued_sidekiq_job
|
||||||
|
.with(anything, collection.account_id)
|
||||||
|
expect(LocalNotificationWorker)
|
||||||
|
.to have_enqueued_sidekiq_job
|
||||||
|
.with(account.id, anything, 'CollectionItem', 'added_to_collection')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -6,21 +6,15 @@ RSpec.describe UpdateCollectionService do
|
|||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
let(:collection) { Fabricate(:collection) }
|
let(:collection) { Fabricate(:collection) }
|
||||||
|
let!(:collection_item) { Fabricate(:collection_item, collection:) }
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
context 'when given valid parameters' do
|
context 'when given valid parameters' do
|
||||||
it 'updates the collection' do
|
it 'updates the collection, sends a notification and federates an `Update` activity' do
|
||||||
subject.call(collection, { name: 'Newly updated name' })
|
expect { subject.call(collection, { name: 'Newly updated name' }) }
|
||||||
|
.to change(collection, :name).to('Newly updated name')
|
||||||
expect(collection.name).to eq 'Newly updated name'
|
.and enqueue_sidekiq_job(LocalNotificationWorker).with(collection_item.account_id, collection.id, collection.class.name, 'collection_update')
|
||||||
end
|
.and enqueue_sidekiq_job(ActivityPub::AccountRawDistributionWorker)
|
||||||
|
|
||||||
context 'when something actually changed' do
|
|
||||||
it 'federates an `Update` activity' do
|
|
||||||
subject.call(collection, { name: 'updated' })
|
|
||||||
|
|
||||||
expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when nothing changed' do
|
context 'when nothing changed' do
|
||||||
@ -28,6 +22,7 @@ RSpec.describe UpdateCollectionService do
|
|||||||
subject.call(collection, { name: collection.name })
|
subject.call(collection, { name: collection.name })
|
||||||
|
|
||||||
expect(ActivityPub::AccountRawDistributionWorker).to_not have_enqueued_sidekiq_job
|
expect(ActivityPub::AccountRawDistributionWorker).to_not have_enqueued_sidekiq_job
|
||||||
|
expect(LocalNotificationWorker).to_not have_enqueued_sidekiq_job
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user