@open-mercato/core 0.4.2-canary-36ab8921da → 0.4.2-canary-07dbc98202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +63 -59
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +155 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +99 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +12 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/commands/users.js +55 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +74 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +76 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +155 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +63 -59
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +160 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/backend/auth/profile/page.meta.ts +8 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +127 -0
- package/src/modules/auth/commands/users.ts +68 -0
- package/src/modules/auth/i18n/de.json +29 -1
- package/src/modules/auth/i18n/en.json +29 -1
- package/src/modules/auth/i18n/es.json +29 -1
- package/src/modules/auth/i18n/pl.json +29 -1
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +75 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +76 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +157 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
2
|
+
|
|
3
|
+
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
type: 'catalog.product.low_stock',
|
|
6
|
+
module: 'catalog',
|
|
7
|
+
titleKey: 'catalog.notifications.product.lowStock.title',
|
|
8
|
+
bodyKey: 'catalog.notifications.product.lowStock.body',
|
|
9
|
+
icon: 'package-x',
|
|
10
|
+
severity: 'warning',
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: 'view',
|
|
14
|
+
labelKey: 'common.view',
|
|
15
|
+
variant: 'outline',
|
|
16
|
+
href: '/backend/catalog/products/{sourceEntityId}',
|
|
17
|
+
icon: 'external-link',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
linkHref: '/backend/catalog/products/{sourceEntityId}',
|
|
21
|
+
expiresAfterHours: 72, // 3 days
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
export default notificationTypes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
3
|
+
import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
4
|
+
import { notificationTypes } from '../notifications'
|
|
5
|
+
|
|
6
|
+
export const metadata = {
|
|
7
|
+
event: 'catalog.product.stock_low',
|
|
8
|
+
persistent: true,
|
|
9
|
+
id: 'catalog:low-stock-notification',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type LowStockPayload = {
|
|
13
|
+
productId: string
|
|
14
|
+
productName: string
|
|
15
|
+
sku?: string | null
|
|
16
|
+
currentStock: number
|
|
17
|
+
threshold: number
|
|
18
|
+
tenantId: string
|
|
19
|
+
organizationId?: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ResolverContext = {
|
|
23
|
+
resolve: <T = unknown>(name: string) => T
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function handle(payload: LowStockPayload, ctx: ResolverContext) {
|
|
27
|
+
try {
|
|
28
|
+
const notificationService = resolveNotificationService(ctx)
|
|
29
|
+
const typeDef = notificationTypes.find((type) => type.type === 'catalog.product.low_stock')
|
|
30
|
+
if (!typeDef) return
|
|
31
|
+
|
|
32
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
33
|
+
requiredFeature: 'catalog.products.manage',
|
|
34
|
+
bodyVariables: {
|
|
35
|
+
productName: payload.productName,
|
|
36
|
+
sku: payload.sku ?? '',
|
|
37
|
+
currentStock: String(payload.currentStock),
|
|
38
|
+
threshold: String(payload.threshold),
|
|
39
|
+
},
|
|
40
|
+
sourceEntityType: 'catalog:product',
|
|
41
|
+
sourceEntityId: payload.productId,
|
|
42
|
+
linkHref: `/backend/catalog/products/${payload.productId}`,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await notificationService.createForFeature(notificationInput, {
|
|
46
|
+
tenantId: payload.tenantId,
|
|
47
|
+
organizationId: payload.organizationId ?? null,
|
|
48
|
+
})
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('[catalog:low-stock-notification] Failed to create notification:', err)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { ModuleCli } from '@open-mercato/shared/modules/registry'
|
|
|
2
2
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
3
|
import type { ModuleConfigService } from './lib/module-config-service'
|
|
4
4
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
5
|
+
import { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, NOTIFICATIONS_DELIVERY_CONFIG_KEY } from '../notifications/lib/deliveryConfig'
|
|
5
6
|
|
|
6
7
|
function envDisablesAutoIndexing(): boolean {
|
|
7
8
|
const raw = process.env.DISABLE_VECTOR_SEARCH_AUTOINDEXING
|
|
@@ -31,6 +32,11 @@ const restoreDefaults: ModuleCli = {
|
|
|
31
32
|
name: 'auto_index_enabled',
|
|
32
33
|
value: defaultEnabled,
|
|
33
34
|
},
|
|
35
|
+
{
|
|
36
|
+
moduleId: 'notifications',
|
|
37
|
+
name: NOTIFICATIONS_DELIVERY_CONFIG_KEY,
|
|
38
|
+
value: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,
|
|
39
|
+
},
|
|
34
40
|
],
|
|
35
41
|
{ force: true },
|
|
36
42
|
)
|
|
@@ -35,6 +35,9 @@ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
|
35
35
|
import type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
|
|
36
36
|
import { E } from '#generated/entities.ids.generated'
|
|
37
37
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
38
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
39
|
+
import { buildNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
40
|
+
import { notificationTypes } from '../notifications'
|
|
38
41
|
|
|
39
42
|
const DEAL_ENTITY_ID = 'customers:customer_deal'
|
|
40
43
|
const dealCrudIndexer: CrudIndexerConfig<CustomerDeal> = {
|
|
@@ -281,6 +284,8 @@ const updateDealCommand: CommandHandler<DealUpdateInput, { dealId: string }> = {
|
|
|
281
284
|
ensureTenantScope(ctx, record.tenantId)
|
|
282
285
|
ensureOrganizationScope(ctx, record.organizationId)
|
|
283
286
|
|
|
287
|
+
const previousStatus = record.status
|
|
288
|
+
|
|
284
289
|
if (parsed.title !== undefined) record.title = parsed.title
|
|
285
290
|
if (parsed.description !== undefined) record.description = parsed.description ?? null
|
|
286
291
|
if (parsed.status !== undefined) record.status = parsed.status ?? record.status
|
|
@@ -319,6 +324,40 @@ const updateDealCommand: CommandHandler<DealUpdateInput, { dealId: string }> = {
|
|
|
319
324
|
indexer: dealCrudIndexer,
|
|
320
325
|
})
|
|
321
326
|
|
|
327
|
+
// Send notifications for deal won/lost status changes
|
|
328
|
+
const newStatus = record.status
|
|
329
|
+
const normalizedStatus = newStatus === 'win' ? 'won' : newStatus === 'loose' ? 'lost' : newStatus
|
|
330
|
+
if (previousStatus !== newStatus && (normalizedStatus === 'won' || normalizedStatus === 'lost') && record.ownerUserId) {
|
|
331
|
+
try {
|
|
332
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
333
|
+
const notificationType = normalizedStatus === 'won' ? 'customers.deal.won' : 'customers.deal.lost'
|
|
334
|
+
const typeDef = notificationTypes.find((type) => type.type === notificationType)
|
|
335
|
+
if (typeDef) {
|
|
336
|
+
const valueDisplay = record.valueAmount && record.valueCurrency
|
|
337
|
+
? `${record.valueCurrency} ${record.valueAmount}`
|
|
338
|
+
: ''
|
|
339
|
+
|
|
340
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
341
|
+
recipientUserId: record.ownerUserId,
|
|
342
|
+
bodyVariables: {
|
|
343
|
+
dealTitle: record.title,
|
|
344
|
+
dealValue: valueDisplay,
|
|
345
|
+
},
|
|
346
|
+
sourceEntityType: 'customers:customer_deal',
|
|
347
|
+
sourceEntityId: record.id,
|
|
348
|
+
linkHref: `/backend/customers/deals/${record.id}`,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
await notificationService.create(notificationInput, {
|
|
352
|
+
tenantId: record.tenantId,
|
|
353
|
+
organizationId: record.organizationId,
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
} catch {
|
|
357
|
+
// Notification creation is non-critical, don't fail the command
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
322
361
|
return { dealId: record.id }
|
|
323
362
|
},
|
|
324
363
|
buildLog: async ({ snapshots, ctx }) => {
|
|
@@ -943,5 +943,9 @@
|
|
|
943
943
|
"customers.workPlan.customerTodos.table.state.empty": "No customer tasks yet.",
|
|
944
944
|
"customers.workPlan.customerTodos.table.error.load": "Failed to load customer tasks.",
|
|
945
945
|
"customers.workPlan.customerTodos.table.export.view": "Exports the current list with filters and visible columns.",
|
|
946
|
-
"customers.workPlan.customerTodos.table.export.full": "Exports every linked task field, including hidden attributes."
|
|
946
|
+
"customers.workPlan.customerTodos.table.export.full": "Exports every linked task field, including hidden attributes.",
|
|
947
|
+
"customers.notifications.deal.won.title": "Deal Won",
|
|
948
|
+
"customers.notifications.deal.won.body": "{dealTitle} has been marked as won{dealValue, select, other { ({dealValue})}}",
|
|
949
|
+
"customers.notifications.deal.lost.title": "Deal Lost",
|
|
950
|
+
"customers.notifications.deal.lost.body": "{dealTitle} has been marked as lost"
|
|
947
951
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
2
|
+
|
|
3
|
+
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
type: 'customers.deal.won',
|
|
6
|
+
module: 'customers',
|
|
7
|
+
titleKey: 'customers.notifications.deal.won.title',
|
|
8
|
+
bodyKey: 'customers.notifications.deal.won.body',
|
|
9
|
+
icon: 'trophy',
|
|
10
|
+
severity: 'success',
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: 'view',
|
|
14
|
+
labelKey: 'common.view',
|
|
15
|
+
variant: 'outline',
|
|
16
|
+
href: '/backend/customers/deals/{sourceEntityId}',
|
|
17
|
+
icon: 'external-link',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
linkHref: '/backend/customers/deals/{sourceEntityId}',
|
|
21
|
+
expiresAfterHours: 168, // 7 days
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'customers.deal.lost',
|
|
25
|
+
module: 'customers',
|
|
26
|
+
titleKey: 'customers.notifications.deal.lost.title',
|
|
27
|
+
bodyKey: 'customers.notifications.deal.lost.body',
|
|
28
|
+
icon: 'x-circle',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
actions: [
|
|
31
|
+
{
|
|
32
|
+
id: 'view',
|
|
33
|
+
labelKey: 'common.view',
|
|
34
|
+
variant: 'outline',
|
|
35
|
+
href: '/backend/customers/deals/{sourceEntityId}',
|
|
36
|
+
icon: 'external-link',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
linkHref: '/backend/customers/deals/{sourceEntityId}',
|
|
40
|
+
expiresAfterHours: 168, // 7 days
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
export default notificationTypes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const features = [
|
|
2
|
+
{ id: 'notifications.view', title: 'View own notifications', module: 'notifications' },
|
|
3
|
+
{ id: 'notifications.create', title: 'Create notifications for others', module: 'notifications' },
|
|
4
|
+
{ id: 'notifications.manage', title: 'Manage all notifications', module: 'notifications' },
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
export default features
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { executeActionSchema } from '../../../data/validators'
|
|
2
|
+
import { actionResultResponseSchema, errorResponseSchema } from '../../openapi'
|
|
3
|
+
import { resolveNotificationContext } from '../../../lib/routeHelpers'
|
|
4
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
5
|
+
|
|
6
|
+
export const metadata = {
|
|
7
|
+
POST: { requireAuth: true },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
11
|
+
const { id } = await params
|
|
12
|
+
const { service, scope } = await resolveNotificationContext(req)
|
|
13
|
+
|
|
14
|
+
const body = await req.json().catch(() => ({}))
|
|
15
|
+
const input = executeActionSchema.parse(body)
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const { notification, result } = await service.executeAction(id, input, scope)
|
|
19
|
+
|
|
20
|
+
const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)
|
|
21
|
+
const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')
|
|
22
|
+
|
|
23
|
+
return Response.json({
|
|
24
|
+
ok: true,
|
|
25
|
+
result,
|
|
26
|
+
href,
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const { t } = await resolveTranslations()
|
|
30
|
+
const fallback = t('notifications.error.action', 'Failed to execute action')
|
|
31
|
+
const message = error instanceof Error && error.message ? error.message : fallback
|
|
32
|
+
return Response.json({ error: message }, { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const openApi = {
|
|
37
|
+
POST: {
|
|
38
|
+
summary: 'Execute notification action',
|
|
39
|
+
tags: ['Notifications'],
|
|
40
|
+
parameters: [
|
|
41
|
+
{
|
|
42
|
+
name: 'id',
|
|
43
|
+
in: 'path',
|
|
44
|
+
required: true,
|
|
45
|
+
schema: { type: 'string', format: 'uuid' },
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
requestBody: {
|
|
49
|
+
required: true,
|
|
50
|
+
content: {
|
|
51
|
+
'application/json': {
|
|
52
|
+
schema: executeActionSchema,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
responses: {
|
|
57
|
+
200: {
|
|
58
|
+
description: 'Action executed successfully',
|
|
59
|
+
content: {
|
|
60
|
+
'application/json': {
|
|
61
|
+
schema: actionResultResponseSchema,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
400: {
|
|
66
|
+
description: 'Action not found or failed',
|
|
67
|
+
content: {
|
|
68
|
+
'application/json': {
|
|
69
|
+
schema: errorResponseSchema,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSingleNotificationActionRoute, createSingleNotificationActionOpenApi } from '../../../lib/routeHelpers'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
PUT: { requireAuth: true },
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const PUT = createSingleNotificationActionRoute('dismiss')
|
|
8
|
+
|
|
9
|
+
export const openApi = createSingleNotificationActionOpenApi(
|
|
10
|
+
'Dismiss notification',
|
|
11
|
+
'Notification dismissed'
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSingleNotificationActionRoute, createSingleNotificationActionOpenApi } from '../../../lib/routeHelpers'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
PUT: { requireAuth: true },
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const PUT = createSingleNotificationActionRoute('markAsRead')
|
|
8
|
+
|
|
9
|
+
export const openApi = createSingleNotificationActionOpenApi(
|
|
10
|
+
'Mark notification as read',
|
|
11
|
+
'Notification marked as read'
|
|
12
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { restoreNotificationSchema } from '../../../data/validators'
|
|
3
|
+
import { resolveNotificationContext } from '../../../lib/routeHelpers'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
PUT: { requireAuth: true },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
|
+
const { id } = await params
|
|
11
|
+
const { service, scope } = await resolveNotificationContext(req)
|
|
12
|
+
|
|
13
|
+
const body = await req.json().catch(() => ({}))
|
|
14
|
+
const input = restoreNotificationSchema.parse(body)
|
|
15
|
+
|
|
16
|
+
await service.restoreDismissed(id, input.status, scope)
|
|
17
|
+
|
|
18
|
+
return Response.json({ ok: true })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const openApi = {
|
|
22
|
+
PUT: {
|
|
23
|
+
summary: 'Restore dismissed notification',
|
|
24
|
+
description: 'Undo a dismissal and restore a notification to read or unread.',
|
|
25
|
+
tags: ['Notifications'],
|
|
26
|
+
parameters: [
|
|
27
|
+
{
|
|
28
|
+
name: 'id',
|
|
29
|
+
in: 'path',
|
|
30
|
+
required: true,
|
|
31
|
+
schema: { type: 'string', format: 'uuid' },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
requestBody: {
|
|
35
|
+
required: false,
|
|
36
|
+
content: {
|
|
37
|
+
'application/json': {
|
|
38
|
+
schema: restoreNotificationSchema,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
responses: {
|
|
43
|
+
200: {
|
|
44
|
+
description: 'Notification restored',
|
|
45
|
+
content: {
|
|
46
|
+
'application/json': {
|
|
47
|
+
schema: z.object({ ok: z.boolean() }),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createBatchNotificationSchema } from '../../data/validators'
|
|
2
|
+
import { createBulkNotificationRoute, createBulkNotificationOpenApi } from '../../lib/routeHelpers'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
POST: { requireAuth: true, requireFeatures: ['notifications.create'] },
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const POST = createBulkNotificationRoute(createBatchNotificationSchema, 'createBatch')
|
|
9
|
+
|
|
10
|
+
export const openApi = createBulkNotificationOpenApi(
|
|
11
|
+
createBatchNotificationSchema,
|
|
12
|
+
'Create batch notifications',
|
|
13
|
+
'Send the same notification to multiple users'
|
|
14
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFeatureNotificationSchema } from '../../data/validators'
|
|
2
|
+
import { createBulkNotificationRoute, createBulkNotificationOpenApi } from '../../lib/routeHelpers'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
POST: { requireAuth: true, requireFeatures: ['notifications.create'] },
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const POST = createBulkNotificationRoute(createFeatureNotificationSchema, 'createForFeature')
|
|
9
|
+
|
|
10
|
+
export const openApi = createBulkNotificationOpenApi(
|
|
11
|
+
createFeatureNotificationSchema,
|
|
12
|
+
'Create notifications for all users with a specific feature/permission',
|
|
13
|
+
'Send the same notification to all users who have the specified feature permission (via role ACL or user ACL). Supports wildcard matching.'
|
|
14
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { resolveNotificationContext } from '../../lib/routeHelpers'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
PUT: { requireAuth: true },
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function PUT(req: Request) {
|
|
9
|
+
const { service, scope } = await resolveNotificationContext(req)
|
|
10
|
+
|
|
11
|
+
const count = await service.markAllAsRead(scope)
|
|
12
|
+
|
|
13
|
+
return Response.json({ ok: true, count })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const openApi = {
|
|
17
|
+
PUT: {
|
|
18
|
+
summary: 'Mark all notifications as read',
|
|
19
|
+
tags: ['Notifications'],
|
|
20
|
+
responses: {
|
|
21
|
+
200: {
|
|
22
|
+
description: 'All notifications marked as read',
|
|
23
|
+
content: {
|
|
24
|
+
'application/json': {
|
|
25
|
+
schema: z.object({
|
|
26
|
+
ok: z.boolean(),
|
|
27
|
+
count: z.number(),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { createCrudOpenApiFactory, createPagedListResponseSchema } from '@open-mercato/shared/lib/openapi/crud'
|
|
3
|
+
import {
|
|
4
|
+
listNotificationsSchema,
|
|
5
|
+
createNotificationSchema,
|
|
6
|
+
executeActionSchema,
|
|
7
|
+
notificationDeliveryConfigSchema,
|
|
8
|
+
} from '../data/validators'
|
|
9
|
+
|
|
10
|
+
export const buildNotificationsCrudOpenApi = createCrudOpenApiFactory({
|
|
11
|
+
defaultTag: 'Notifications',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const notificationItemSchema = z.object({
|
|
15
|
+
id: z.string().uuid(),
|
|
16
|
+
type: z.string(),
|
|
17
|
+
title: z.string(),
|
|
18
|
+
body: z.string().nullable().optional(),
|
|
19
|
+
titleKey: z.string().nullable().optional(),
|
|
20
|
+
bodyKey: z.string().nullable().optional(),
|
|
21
|
+
titleVariables: z.record(z.string(), z.string()).nullable().optional(),
|
|
22
|
+
bodyVariables: z.record(z.string(), z.string()).nullable().optional(),
|
|
23
|
+
icon: z.string().nullable().optional(),
|
|
24
|
+
severity: z.string(),
|
|
25
|
+
status: z.string(),
|
|
26
|
+
actions: z.array(z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
label: z.string(),
|
|
29
|
+
labelKey: z.string().optional(),
|
|
30
|
+
variant: z.string().optional(),
|
|
31
|
+
icon: z.string().optional(),
|
|
32
|
+
})),
|
|
33
|
+
primaryActionId: z.string().optional(),
|
|
34
|
+
sourceModule: z.string().nullable().optional(),
|
|
35
|
+
sourceEntityType: z.string().nullable().optional(),
|
|
36
|
+
sourceEntityId: z.string().uuid().nullable().optional(),
|
|
37
|
+
linkHref: z.string().nullable().optional(),
|
|
38
|
+
createdAt: z.string(),
|
|
39
|
+
readAt: z.string().nullable().optional(),
|
|
40
|
+
actionTaken: z.string().nullable().optional(),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const okResponseSchema = z.object({
|
|
44
|
+
ok: z.boolean(),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export const errorResponseSchema = z.object({
|
|
48
|
+
error: z.string(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const unreadCountResponseSchema = z.object({
|
|
52
|
+
unreadCount: z.number(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const actionResultResponseSchema = z.object({
|
|
56
|
+
ok: z.boolean(),
|
|
57
|
+
result: z.unknown().optional(),
|
|
58
|
+
href: z.string().optional(),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export const notificationSettingsResponseSchema = z.object({
|
|
62
|
+
settings: notificationDeliveryConfigSchema,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const notificationSettingsUpdateResponseSchema = z.object({
|
|
66
|
+
ok: z.boolean(),
|
|
67
|
+
settings: notificationDeliveryConfigSchema,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
createPagedListResponseSchema,
|
|
72
|
+
listNotificationsSchema,
|
|
73
|
+
createNotificationSchema,
|
|
74
|
+
executeActionSchema,
|
|
75
|
+
notificationDeliveryConfigSchema,
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRoleNotificationSchema } from '../../data/validators'
|
|
2
|
+
import { createBulkNotificationRoute, createBulkNotificationOpenApi } from '../../lib/routeHelpers'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
POST: { requireAuth: true, requireFeatures: ['notifications.create'] },
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const POST = createBulkNotificationRoute(createRoleNotificationSchema, 'createForRole')
|
|
9
|
+
|
|
10
|
+
export const openApi = createBulkNotificationOpenApi(
|
|
11
|
+
createRoleNotificationSchema,
|
|
12
|
+
'Create notifications for all users in a role',
|
|
13
|
+
'Send the same notification to all users who have the specified role within the organization'
|
|
14
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/core'
|
|
3
|
+
import { Notification } from '../data/entities'
|
|
4
|
+
import { listNotificationsSchema, createNotificationSchema } from '../data/validators'
|
|
5
|
+
import { toNotificationDto } from '../lib/notificationMapper'
|
|
6
|
+
import { resolveNotificationContext } from '../lib/routeHelpers'
|
|
7
|
+
import {
|
|
8
|
+
buildNotificationsCrudOpenApi,
|
|
9
|
+
createPagedListResponseSchema,
|
|
10
|
+
notificationItemSchema,
|
|
11
|
+
} from './openapi'
|
|
12
|
+
|
|
13
|
+
export const metadata = {
|
|
14
|
+
GET: { requireAuth: true },
|
|
15
|
+
POST: { requireAuth: true, requireFeatures: ['notifications.create'] },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function GET(req: Request) {
|
|
19
|
+
const { ctx, scope } = await resolveNotificationContext(req)
|
|
20
|
+
const em = ctx.container.resolve('em') as EntityManager
|
|
21
|
+
|
|
22
|
+
const url = new URL(req.url)
|
|
23
|
+
const queryParams = Object.fromEntries(url.searchParams.entries())
|
|
24
|
+
const input = listNotificationsSchema.parse(queryParams)
|
|
25
|
+
|
|
26
|
+
const filters: Record<string, unknown> = {
|
|
27
|
+
recipientUserId: scope.userId,
|
|
28
|
+
tenantId: scope.tenantId,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (input.status) {
|
|
32
|
+
filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status
|
|
33
|
+
} else {
|
|
34
|
+
filters.status = { $ne: 'dismissed' }
|
|
35
|
+
}
|
|
36
|
+
if (input.type) {
|
|
37
|
+
filters.type = input.type
|
|
38
|
+
}
|
|
39
|
+
if (input.severity) {
|
|
40
|
+
filters.severity = input.severity
|
|
41
|
+
}
|
|
42
|
+
if (input.sourceEntityType) {
|
|
43
|
+
filters.sourceEntityType = input.sourceEntityType
|
|
44
|
+
}
|
|
45
|
+
if (input.sourceEntityId) {
|
|
46
|
+
filters.sourceEntityId = input.sourceEntityId
|
|
47
|
+
}
|
|
48
|
+
if (input.since) {
|
|
49
|
+
filters.createdAt = { $gt: new Date(input.since) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [notifications, total] = await Promise.all([
|
|
53
|
+
em.find(Notification, filters, {
|
|
54
|
+
orderBy: { createdAt: 'desc' },
|
|
55
|
+
limit: input.pageSize,
|
|
56
|
+
offset: (input.page - 1) * input.pageSize,
|
|
57
|
+
}),
|
|
58
|
+
em.count(Notification, filters),
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
const items = notifications.map(toNotificationDto)
|
|
62
|
+
|
|
63
|
+
return Response.json({
|
|
64
|
+
items,
|
|
65
|
+
total,
|
|
66
|
+
page: input.page,
|
|
67
|
+
pageSize: input.pageSize,
|
|
68
|
+
totalPages: Math.ceil(total / input.pageSize),
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function POST(req: Request) {
|
|
73
|
+
const { service, scope } = await resolveNotificationContext(req)
|
|
74
|
+
|
|
75
|
+
const body = await req.json().catch(() => ({}))
|
|
76
|
+
const input = createNotificationSchema.parse(body)
|
|
77
|
+
|
|
78
|
+
const notification = await service.create(input, scope)
|
|
79
|
+
|
|
80
|
+
return Response.json({ id: notification.id }, { status: 201 })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const openApi = buildNotificationsCrudOpenApi({
|
|
84
|
+
resourceName: 'Notification',
|
|
85
|
+
querySchema: listNotificationsSchema,
|
|
86
|
+
listResponseSchema: createPagedListResponseSchema(notificationItemSchema),
|
|
87
|
+
create: {
|
|
88
|
+
schema: createNotificationSchema,
|
|
89
|
+
responseSchema: z.object({ id: z.string().uuid() }),
|
|
90
|
+
description: 'Creates a notification for a user.',
|
|
91
|
+
},
|
|
92
|
+
})
|