summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2025-06-14 16:56:32 +0100
committerdakkar <dakkar@thenautilus.net>2025-06-14 17:02:00 +0100
commitc882a4294dd4039329e285826701a26e0e9337c7 (patch)
tree66dc20feb6f7211b8ec75ff679cdf06612185207
parentmerge: Throw S3 errors to prevent silent failures (resolves #697) (!1115) (diff)
downloadsharkey-c882a4294dd4039329e285826701a26e0e9337c7.tar.gz
sharkey-c882a4294dd4039329e285826701a26e0e9337c7.tar.bz2
sharkey-c882a4294dd4039329e285826701a26e0e9337c7.zip
group notifications regardless of when they happened - fix #633
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications-grouped.ts87
1 files changed, 61 insertions, 26 deletions
diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
index b9c41b057d..4b0b9d0884 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
@@ -104,53 +104,88 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// grouping
- let groupedNotifications = [notifications[0]] as MiGroupedNotification[];
- for (let i = 1; i < notifications.length; i++) {
+ const groupedNotifications : MiGroupedNotification[] = [];
+ // keep track of where reaction / renote notifications are, by note id
+ const reactionIdxByNoteId = new Map();
+ const renoteIdxByNoteId = new Map();
+
+ // group notifications by type+note; notice that we don't try to
+ // split groups if they span a long stretch of time, because
+ // it's probably overkill: if the user has very few
+ // notifications, there should be very little difference; if the
+ // user has many notifications, the pagination will break the
+ // groups
+
+ // scan `notifications` newest-to-oldest
+ for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i];
- const prev = notifications[i - 1];
- let prevGroupedNotification = groupedNotifications.at(-1)!;
- if (prev.type === 'reaction' && notification.type === 'reaction' && prev.noteId === notification.noteId) {
- if (prevGroupedNotification.type !== 'reaction:grouped') {
- groupedNotifications[groupedNotifications.length - 1] = {
+ if (notification.type === 'reaction') {
+ const reactionIdx = reactionIdxByNoteId.get(notification.noteId);
+ if (reactionIdx === undefined) {
+ // first reaction to this note that we see, add it as-is
+ // and remember where we put it
+ groupedNotifications.push(notification);
+ reactionIdxByNoteId.set(notification.noteId, groupedNotifications.length - 1);
+ continue;
+ }
+
+ let prevReaction = groupedNotifications[reactionIdx];
+ // if the previous reaction is not a group, make it into one
+ if (prevReaction.type !== 'reaction:grouped') {
+ prevReaction = groupedNotifications[reactionIdx] = {
type: 'reaction:grouped',
- id: '',
- createdAt: prev.createdAt,
- noteId: prev.noteId!,
+ id: prevReaction.id, // this will be the newest id in this group
+ createdAt: prevReaction.createdAt,
+ noteId: prevReaction.noteId!,
reactions: [{
- userId: prev.notifierId!,
- reaction: prev.reaction!,
+ userId: prevReaction.notifierId!,
+ reaction: prevReaction.reaction!,
}],
};
- prevGroupedNotification = groupedNotifications.at(-1)!;
}
- (prevGroupedNotification as FilterUnionByProperty<MiGroupedNotification, 'type', 'reaction:grouped'>).reactions.push({
+ // add this new reaction to the existing group
+ (prevReaction as FilterUnionByProperty<MiGroupedNotification, 'type', 'reaction:grouped'>).reactions.push({
userId: notification.notifierId!,
reaction: notification.reaction!,
});
- prevGroupedNotification.id = notification.id;
continue;
}
- if (prev.type === 'renote' && notification.type === 'renote' && prev.targetNoteId === notification.targetNoteId) {
- if (prevGroupedNotification.type !== 'renote:grouped') {
- groupedNotifications[groupedNotifications.length - 1] = {
+
+ if (notification.type === 'renote') {
+ const renoteIdx = renoteIdxByNoteId.get(notification.targetNoteId);
+ if (renoteIdx === undefined) {
+ // first renote of this note that we see, add it as-is and
+ // remember where we put it
+ groupedNotifications.push(notification);
+ renoteIdxByNoteId.set(notification.targetNoteId, groupedNotifications.length - 1);
+ continue;
+ }
+
+ let prevRenote = groupedNotifications[renoteIdx];
+ // if the previous renote is not a group, make it into one
+ if (prevRenote.type !== 'renote:grouped') {
+ prevRenote = groupedNotifications[renoteIdx] = {
type: 'renote:grouped',
- id: '',
- createdAt: notification.createdAt,
- noteId: prev.noteId!,
- userIds: [prev.notifierId!],
+ id: prevRenote.id, // this will be the newest id in this group
+ createdAt: prevRenote.createdAt,
+ noteId: prevRenote.noteId!,
+ userIds: [prevRenote.notifierId!],
};
- prevGroupedNotification = groupedNotifications.at(-1)!;
}
- (prevGroupedNotification as FilterUnionByProperty<MiGroupedNotification, 'type', 'renote:grouped'>).userIds.push(notification.notifierId!);
- prevGroupedNotification.id = notification.id;
+ // add this new renote to the existing group
+ (prevRenote as FilterUnionByProperty<MiGroupedNotification, 'type', 'renote:grouped'>).userIds.push(notification.notifierId!);
continue;
}
+ // not a groupable notification, just push it
groupedNotifications.push(notification);
}
- groupedNotifications = groupedNotifications.slice(0, ps.limit);
+ // sort the groups by their id, newest first
+ groupedNotifications.sort(
+ (a, b) => a.id < b.id ? 1 : a.id > b.id ? -1 : 0,
+ );
return await this.notificationEntityService.packGroupedMany(groupedNotifications, me.id);
});