Files
speckle-server/packages/server/modules/notifications/tasks/delayedNotifications.ts
T
Daniel Gak Anagrov 3ca4a11ca3 feat(notifications): basic listener structure, notification record, delayed mechanism (#5432)
* feat: basic notification listener sturcuture

* feat: clean up generated gql

* chore: edited structure

* feat: added basic repo

* feat: ported comment email to job queue

* feat: ported stream access request accepted

* feat: added notification insertion

* fix: minor typings

* feat: delayed notifications

* updated types

* feat: fixed gql

* notifications are listed

* index on notifications

* feat: while loop skiping for update locked

* delayed notification for access request

* take into account user prefrences

* on comment view, notification is marked as read

* feat: added gql notifications

* feat: avoid raising errors

* fix: error added scopes

* fix: mr comments

* fix: cursor and service method

* feat: added stronger types to notifications and versioning logic

* minor: rows updated
2025-10-06 12:19:12 +01:00

106 lines
3.3 KiB
TypeScript

import { db } from '@/db/knex'
import {
acquireTaskLockFactory,
releaseTaskLockFactory
} from '@/modules/core/repositories/scheduledTasks'
import { scheduleExecutionFactory } from '@/modules/core/services/taskScheduler'
import type { Logger } from '@/observability/logging'
import {
getNextEmailNotificationFactory,
updateUserNotificationFactory
} from '@/modules/notifications/repositories/userNotification'
import { NotificationType } from '@speckle/shared/notifications'
import MentionedInCommentHandler from '@/modules/notifications/tasks/handlers/mentionedInComment'
import StreamAccessRequestApprovedHandler from '@/modules/notifications/tasks/handlers/streamAccessRequestApproved'
import NewStreamAccessRequestHandler from '@/modules/notifications/tasks/handlers/newStreamAccessRequest'
import { ensureNotificationToLatestVersion } from '@/modules/notifications/helpers/toLatestVersion'
type EmailNotificationResult = { notificationId: string } | null
const handleNextEmailNotification = async (deps: {
logger: Logger
}): Promise<EmailNotificationResult> =>
db.transaction(async (trx) => {
const baseNotification = await getNextEmailNotificationFactory({ db: trx })()
if (!baseNotification) return null
const notification = ensureNotificationToLatestVersion(baseNotification)
if (!notification) return null
try {
switch (notification.type) {
case NotificationType.MentionedInComment:
await MentionedInCommentHandler(notification)
break
case NotificationType.StreamAccessRequestApproved:
await StreamAccessRequestApprovedHandler(notification)
break
case NotificationType.NewStreamAccessRequest:
await NewStreamAccessRequestHandler(notification)
break
default:
deps.logger.error(
{
type: notification.type,
notificationId: notification.id
},
`No handler scheduled notification type. Skipping.`
)
break
}
} catch (error) {
deps.logger.error(
{
error,
type: notification.type,
notificationId: notification.id
},
`Error handling notification. Skipping.`
)
}
await updateUserNotificationFactory({ db: trx })({
id: notification.id,
userId: notification.userId,
update: {
sendEmailAt: null,
updatedAt: new Date()
}
})
return { notificationId: notification.id }
})
export const emitDelayedEmailNotifications = async (deps: { logger: Logger }) => {
let result: EmailNotificationResult
const MAX_ITERATIONS = 10_000
let iterationCount = 0
do {
if (iterationCount++ >= MAX_ITERATIONS) {
deps.logger.error(`Reached max iteration limit of ${MAX_ITERATIONS}.`)
break
}
result = await handleNextEmailNotification(deps)
} while (result)
}
export const scheduleDelayedEmailNotifications = async () => {
const scheduleExecution = scheduleExecutionFactory({
acquireTaskLock: acquireTaskLockFactory({ db }),
releaseTaskLock: releaseTaskLockFactory({ db })
})
const everyMin = '*/1 * * * *'
return scheduleExecution(
everyMin,
'DelayedEmailNotifications',
async (_scheduledTime, { logger }) => {
await emitDelayedEmailNotifications({
logger
})
}
)
}