Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,21 @@ export class Announcement extends BaseNotification<AnnouncementNotificationRow>
this.identityDB,
userIds
)
// Absent channels (older rows / non-dashboard callers) default to 'both'.
const channels = this.notification.data.notification_channels ?? 'both'
const sendPush = channels === 'push' || channels === 'both'
const sendEmail = channels === 'email' || channels === 'both'
for (const userId of userIds) {
await this.broadcastPushNotificationAnnouncements(
userId,
userNotificationSettings,
isBrowserPushEnabled
)
await this.broadcastEmailAnnouncements(userId, userNotificationSettings)
if (sendPush) {
await this.broadcastPushNotificationAnnouncements(
userId,
userNotificationSettings,
isBrowserPushEnabled
)
}
if (sendEmail) {
await this.broadcastEmailAnnouncements(userId, userNotificationSettings)
}
}
}

Expand Down
31 changes: 28 additions & 3 deletions apps/notifications/src/server/routes/sendNotification.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Router, Request, Response } from 'express'
import { Knex } from 'knex'
import { logger } from '../../logger'
import { NotificationChannel } from '../../types/notifications'

const containsHtml = (value: string): boolean => /<[^>]*>/.test(value)
const NOTIFICATION_CHANNELS: readonly NotificationChannel[] = [
'email',
'push',
'both'
]
const normalizeRoute = (value: string): string | null => {
const input = value.trim()
if (!input) return null
Expand Down Expand Up @@ -34,14 +40,32 @@ export function createSendNotificationRouter(discoveryDb: Knex): Router {
res.status(401).json({ error: 'Unauthorized' })
return
}
const { title, body, image_url, route, userIds, notification_campaign_id } =
req.body
const {
title,
body,
image_url,
route,
userIds,
notification_campaign_id,
notificationTypes
} = req.body
if (!title || !body || !Array.isArray(userIds) || userIds.length === 0) {
res.status(400).json({
error: 'Missing required fields: title, body, userIds (non-empty array)'
})
return
}
// Default to 'both' so existing callers that omit notificationTypes are unaffected.
const channels: NotificationChannel =
notificationTypes == null ? 'both' : notificationTypes
if (!NOTIFICATION_CHANNELS.includes(channels)) {
res.status(400).json({
error: `Invalid notificationTypes. Use one of: ${NOTIFICATION_CHANNELS.join(
', '
)}`
})
return
}

try {
const titleText = String(title).trim()
Expand Down Expand Up @@ -102,7 +126,8 @@ export function createSendNotificationRouter(discoveryDb: Knex): Router {
push_body: bodyText,
...(normalizedRoute ? { route: normalizedRoute } : {}),
...(imageUrlText ? { image_url: imageUrlText } : {}),
...(campaignIdRaw ? { notification_campaign_id: campaignIdRaw } : {})
...(campaignIdRaw ? { notification_campaign_id: campaignIdRaw } : {}),
notification_channels: channels
}
})
logger.info(
Expand Down
5 changes: 5 additions & 0 deletions apps/notifications/src/types/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ export type TrendingUndergroundNotification = {
time_range: string
}

/** Which delivery channels an announcement should fire. Defaults to `both`. */
export type NotificationChannel = 'email' | 'push' | 'both'

export type AnnouncementNotification = {
title: string
short_description: string
Expand All @@ -258,6 +261,8 @@ export type AnnouncementNotification = {
image_url?: string
/** First-party campaign id (e.g. notifications-dashboard `announcements.id`). */
notification_campaign_id?: string
/** Delivery channels to fire. Absent is treated as `both` for back-compat. */
notification_channels?: NotificationChannel
}

export type USDCPurchaseBuyerNotification = {
Expand Down
Loading