diff --git a/api/paidAction/itemCreate.js b/api/paidAction/itemCreate.js index 7ed723a8..086f4b70 100644 --- a/api/paidAction/itemCreate.js +++ b/api/paidAction/itemCreate.js @@ -1,5 +1,5 @@ import { ANON_ITEM_SPAM_INTERVAL, ITEM_SPAM_INTERVAL, PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants' -import { notifyItemMention, notifyItemParents, notifyMention, notifyTerritorySubscribers, notifyUserSubscribers } from '@/lib/webPush' +import { notifyItemMention, notifyItemParents, notifyMention, notifyTerritorySubscribers, notifyUserSubscribers, notifyThreadSubscribers } from '@/lib/webPush' import { getItemMentions, getMentions, performBotBehavior } from './lib/item' import { msatsToSats, satsToMsats } from '@/lib/format' import { GqlInputError } from '@/lib/error' @@ -259,6 +259,7 @@ export async function nonCriticalSideEffects ({ invoice, id }, { models }) { if (item.parentId) { notifyItemParents({ item, models }).catch(console.error) + notifyThreadSubscribers({ models, item }).catch(console.error) } for (const { userId } of item.mentions) { notifyMention({ models, item, userId }).catch(console.error) diff --git a/lib/webPush.js b/lib/webPush.js index 9e78ad2d..2a5e9cca 100644 --- a/lib/webPush.js +++ b/lib/webPush.js @@ -202,6 +202,44 @@ export const notifyTerritorySubscribers = async ({ models, item }) => { } } +export const notifyThreadSubscribers = async ({ models, item }) => { + try { + const author = await models.user.findUnique({ where: { id: item.userId } }) + + const subscribers = await models.$queryRaw` + SELECT DISTINCT "ThreadSubscription"."userId" FROM "ThreadSubscription" + JOIN users ON users.id = "ThreadSubscription"."userId" + JOIN "Reply" r ON "ThreadSubscription"."itemId" = r."ancestorId" + WHERE r."itemId" = ${item.id} + -- don't send notifications for own items + AND r."userId" <> "ThreadSubscription"."userId" + -- send notifications for all levels? + AND CASE WHEN users."noteAllDescendants" THEN TRUE ELSE r.level = 1 END + -- muted? + AND NOT EXISTS (SELECT 1 FROM "Mute" m WHERE m."muterId" = users.id AND m."mutedId" = r."userId") + -- already received notification as reply to self? + AND NOT EXISTS ( + SELECT 1 FROM "Item" i + JOIN "Item" p ON p.path @> i.path + WHERE i.id = ${item.parentId} AND p."userId" = "ThreadSubscription"."userId" AND users."noteAllDescendants" + )` + + await Promise.allSettled(subscribers.map(({ userId }) => + sendUserNotification(userId, { + // we reuse the same payload as for user subscriptions because they use the same title+body we want to use here + // so we should also merge them together (= same tag+data) to avoid confusion + title: `@${author.name} replied to a post`, + body: item.text, + item, + data: { followeeName: author.name, subType: 'COMMENT' }, + tag: `FOLLOW-${author.id}-COMMENT` + }) + )) + } catch (err) { + console.error(err) + } +} + export const notifyItemParents = async ({ models, item }) => { try { const user = await models.user.findUnique({ where: { id: item.userId } })