@gzl10/nexus-plugin-notifications 0.16.3 → 0.16.5
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/index.d.ts +12 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/llms.txt +63 -0
- package/package.json +8 -18
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModuleContext, EventEntityDefinition, PluginManifest } from '@gzl10/nexus-sdk';
|
|
1
|
+
import { ModuleContext, EventEntityDefinition, ModuleManifest, PluginManifest } from '@gzl10/nexus-sdk';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -239,6 +239,16 @@ declare const notificationEntity: EventEntityDefinition;
|
|
|
239
239
|
*/
|
|
240
240
|
declare const notificationReadEntity: EventEntityDefinition;
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* @module notifications
|
|
244
|
+
* @description Real-time notifications with Socket.IO delivery and persistence
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Real-time notifications module
|
|
249
|
+
*/
|
|
250
|
+
declare const notificationsModule: ModuleManifest;
|
|
251
|
+
|
|
242
252
|
/**
|
|
243
253
|
* @module @gzl10/nexus-plugin-notifications
|
|
244
254
|
* @description Real-time in-app notifications via Socket.IO
|
|
@@ -246,4 +256,4 @@ declare const notificationReadEntity: EventEntityDefinition;
|
|
|
246
256
|
|
|
247
257
|
declare const notificationsPlugin: PluginManifest;
|
|
248
258
|
|
|
249
|
-
export { NOTIFICATION_PRIORITY_OPTIONS, NOTIFICATION_TARGET_OPTIONS, NOTIFICATION_TYPE_OPTIONS, type Notification, type NotificationPayload, type NotificationPriority, NotificationService, type NotificationTarget, type NotificationType, type SendNotificationInput, notificationsPlugin as default, getNotificationService, initNotificationService, notificationEntity, notificationReadEntity, notificationsPlugin, sendNotificationInputSchema };
|
|
259
|
+
export { NOTIFICATION_PRIORITY_OPTIONS, NOTIFICATION_TARGET_OPTIONS, NOTIFICATION_TYPE_OPTIONS, type Notification, type NotificationPayload, type NotificationPriority, NotificationService, type NotificationTarget, type NotificationType, type SendNotificationInput, notificationsPlugin as default, getNotificationService, initNotificationService, notificationEntity, notificationReadEntity, notificationsModule, notificationsPlugin, sendNotificationInputSchema };
|
package/dist/index.js
CHANGED
|
@@ -356,6 +356,7 @@ var notificationEntity = {
|
|
|
356
356
|
var notificationReadEntity = {
|
|
357
357
|
type: "event",
|
|
358
358
|
immutable: true,
|
|
359
|
+
expose: false,
|
|
359
360
|
table: "notification_reads",
|
|
360
361
|
label: { en: "Notification Read", es: "Lectura de notificaci\xF3n" },
|
|
361
362
|
labelPlural: { en: "Notification Reads", es: "Lecturas de notificaciones" },
|
|
@@ -587,6 +588,7 @@ export {
|
|
|
587
588
|
initNotificationService,
|
|
588
589
|
notificationEntity,
|
|
589
590
|
notificationReadEntity,
|
|
591
|
+
notificationsModule,
|
|
590
592
|
notificationsPlugin,
|
|
591
593
|
sendNotificationInputSchema
|
|
592
594
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/notifications.entity.ts","../src/notifications.service.ts","../src/notifications.types.ts","../src/notifications.routes.ts","../src/notifications.socket.ts","../src/notifications-module.ts"],"sourcesContent":["/**\n * @module @gzl10/nexus-plugin-notifications\n * @description Real-time in-app notifications via Socket.IO\n */\n\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { PluginManifest } from '@gzl10/nexus-sdk'\nimport { notificationsModule } from './notifications-module.js'\n\nconst pkg = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'package.json'), 'utf-8')) as { name: string; version: string }\n\nexport { NotificationService, getNotificationService, initNotificationService } from './notifications.service.js'\n\nexport type {\n NotificationType,\n NotificationPriority,\n NotificationTarget,\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\nexport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\n\nexport { notificationEntity, notificationReadEntity } from './notifications.entity.js'\n\nexport const notificationsPlugin: PluginManifest = {\n name: pkg.name,\n code: 'ntf',\n label: { en: 'Notifications', es: 'Notificaciones' },\n icon: 'mdi:bell-outline',\n category: 'messaging',\n version: pkg.version,\n description: {\n en: 'Real-time in-app notifications via Socket.IO',\n es: 'Notificaciones in-app en tiempo real vía Socket.IO'\n },\n modules: [notificationsModule]\n}\n\nexport default notificationsPlugin\n","import type { EventEntityDefinition, ActionDefinition } from '@gzl10/nexus-sdk'\nimport { useIdField, useTextField, useTextareaField, useSelectField, useUrlField, useDatetimeField, useExpiresAtField } from '@gzl10/nexus-sdk/fields'\nimport { getNotificationService } from './notifications.service.js'\nimport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\nimport type { SendNotificationInput } from './notifications.types.js'\n\n/**\n * Notification (event) - ephemeral notifications with TTL\n */\nexport const notificationEntity: EventEntityDefinition = {\n type: 'event',\n realtime: 'sync',\n immutable: true,\n table: 'notifications',\n routePrefix: '/',\n label: { en: 'Notification', es: 'Notificación' },\n labelPlural: { en: 'Notifications', es: 'Notificaciones' },\n labelField: 'title',\n timestamps: true,\n retention: { days: 30 },\n calendarFrom: 'expires_at',\n fields: {\n id: useIdField(),\n title: useTextField({ label: { en: 'Title', es: 'Título' }, required: true }),\n message: useTextareaField({ label: { en: 'Message', es: 'Mensaje' }, required: true }),\n type: useSelectField({\n label: { en: 'Type', es: 'Tipo' },\n options: [...NOTIFICATION_TYPE_OPTIONS],\n defaultValue: 'info',\n required: true,\n size: 20\n }),\n priority: useSelectField({\n label: { en: 'Priority', es: 'Prioridad' },\n options: [...NOTIFICATION_PRIORITY_OPTIONS],\n defaultValue: 'normal',\n required: true,\n size: 10\n }),\n target_type: useSelectField({\n label: { en: 'Target Type', es: 'Tipo de destino' },\n options: [...NOTIFICATION_TARGET_OPTIONS],\n required: true,\n size: 20\n }),\n target_value: useTextField({ label: { en: 'Target Value', es: 'Valor del destino' } }),\n link: useUrlField({ label: { en: 'Link', es: 'Enlace' } }),\n expires_at: useExpiresAtField()\n },\n actions: [\n {\n key: 'delete',\n scope: 'row',\n icon: 'mdi:delete-outline',\n label: { en: 'Delete Notification', es: 'Eliminar notificación' },\n method: 'DELETE',\n handler: async (_ctx, input: unknown) => {\n const record = (input as Record<string, unknown>)?._record as Record<string, unknown> | undefined\n const id = typeof record?.id === 'string' ? record.id : undefined\n if (!id) return { deleted: false }\n const service = getNotificationService()\n const deleted = await service.delete(id)\n return { deleted }\n },\n casl: {\n subject: 'Notification',\n permissions: {\n ADMIN: { actions: ['execute'] }\n }\n }\n } satisfies ActionDefinition\n ],\n casl: {\n subject: 'Notification',\n permissions: {\n MANAGER: { actions: ['read'] },\n EDITOR: { actions: ['read'] },\n CONTRIBUTOR: { actions: ['read'] },\n USER: [\n { actions: ['read'], conditions: { target_type: 'user', target_value: '${user.id}' } },\n { actions: ['read'], conditions: { target_type: 'all' } },\n { actions: ['read'], conditions: { target_type: 'authenticated' } },\n ],\n VIEWER: [\n { actions: ['read'], conditions: { target_type: 'user', target_value: '${user.id}' } },\n { actions: ['read'], conditions: { target_type: 'all' } },\n { actions: ['read'], conditions: { target_type: 'authenticated' } },\n ],\n SUPPORT: { actions: ['read'] }\n }\n }\n}\n\n/**\n * NotificationRead (event) - read log\n */\nexport const notificationReadEntity: EventEntityDefinition = {\n type: 'event',\n immutable: true,\n table: 'notification_reads',\n label: { en: 'Notification Read', es: 'Lectura de notificación' },\n labelPlural: { en: 'Notification Reads', es: 'Lecturas de notificaciones' },\n labelField: 'notification_id',\n retention: { days: 90 },\n fields: {\n id: useIdField(),\n notification_id: useTextField({ label: { en: 'Notification ID', es: 'ID de notificación' }, required: true, size: 26, hidden: true }),\n user_id: useTextField({ label: { en: 'User ID', es: 'ID de usuario' }, required: true, size: 26, hidden: true }),\n read_at: useDatetimeField({ label: { en: 'Read At', es: 'Leído el' }, required: true })\n },\n indexes: [\n { columns: ['notification_id', 'user_id'], unique: true }\n ],\n casl: {\n subject: 'NotificationRead',\n permissions: {\n ADMIN: { actions: ['manage'] }\n }\n }\n}\n\n/**\n * SendNotification (action) - send notification\n */\nexport const sendNotificationAction: ActionDefinition = {\n key: \"send\",\n scope: \"module\",\n label: { en: \"Send Notification\", es: \"Enviar notificación\" },\n output: {},\n inputSchema: sendNotificationInputSchema,\n handler: async (_ctx, input: unknown) => {\n // Usar getNotificationService() directamente, no ctx.services\n // porque createModuleRouters() sobrescribe ctx.services['notifications']\n // con el EventEntityService de notificationEntity\n const service = getNotificationService();\n return service.send(input as SendNotificationInput);\n },\n input: {\n title: useTextField({ label: { en: 'Title', es: 'Título' }, required: true }),\n message: useTextareaField({ label: { en: 'Message', es: 'Mensaje' }, required: true }),\n target_type: useSelectField({\n label: { en: 'Target Type', es: 'Tipo de destino' },\n options: [...NOTIFICATION_TARGET_OPTIONS],\n required: true,\n }),\n target_value: useTextField({ label: { en: 'Target Value', es: 'Valor del destino' } }),\n type: useSelectField({\n label: { en: 'Type', es: 'Tipo' },\n options: [...NOTIFICATION_TYPE_OPTIONS],\n }),\n priority: useSelectField({\n label: { en: 'Priority', es: 'Prioridad' },\n options: [...NOTIFICATION_PRIORITY_OPTIONS],\n }),\n link: useUrlField({ label: { en: 'Link', es: 'Enlace' } }),\n expires_at: useDatetimeField({ label: { en: 'Expires At', es: 'Expira el' } }),\n },\n casl: {\n subject: \"Notification\",\n permissions: {\n ADMIN: { actions: [\"execute\"] },\n },\n },\n};\n","import type { Knex } from 'knex'\nimport type { Logger } from 'pino'\nimport type { ModuleContext } from '@gzl10/nexus-sdk'\nimport type {\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\n/**\n * Notifications service\n */\nexport class NotificationService {\n private db: Knex\n private logger: Logger\n private generateId: () => string\n private nowTimestamp: (db: Knex) => string\n private formatTimestamp: (db: Knex, date?: Date) => string\n private safeJsonParse: ModuleContext['core']['safeJsonParse']\n private socket: ModuleContext['core']['socket']\n private eventsNotify: ModuleContext['events']['notify']\n private t: (name: string) => string\n\n constructor(ctx: ModuleContext) {\n this.db = ctx.db.knex\n this.logger = ctx.core.logger.child({ service: 'notifications' })\n this.generateId = ctx.core.generateId\n this.nowTimestamp = ctx.db.nowTimestamp\n this.formatTimestamp = ctx.db.formatTimestamp\n this.safeJsonParse = ctx.core.safeJsonParse\n this.socket = ctx.core.socket\n this.eventsNotify = ctx.events.notify.bind(ctx.events)\n this.t = ctx.db.t\n }\n\n /**\n * Sends a notification\n */\n async send(options: SendNotificationInput): Promise<{ id: string; sent: number }> {\n const { target_type, target_value } = options\n let sent = 0\n\n const type = options.type || 'info'\n const priority = options.priority || 'normal'\n\n // 1. Persistir notificación\n const id = this.generateId()\n const now = this.nowTimestamp(this.db)\n\n await this.db(this.t('notifications')).insert({\n id,\n title: options.title,\n message: options.message,\n type,\n priority,\n target_type,\n target_value: target_value || null,\n link: options.link || null,\n expires_at: options.expires_at ? this.formatTimestamp(this.db, new Date(options.expires_at)) : null,\n created_at: now\n })\n\n // 2. Emitir por Socket.IO si está inicializado (via ctx.core.socket helpers)\n if (this.socket.isInitialized()) {\n const payload: NotificationPayload = {\n id,\n title: options.title,\n message: options.message,\n type,\n priority,\n link: options.link || undefined,\n timestamp: now\n }\n\n switch (target_type) {\n case 'all':\n this.socket.emitToAll('notification', payload)\n sent = 1 // broadcast, exact count not critical\n break\n\n case 'authenticated':\n this.socket.emitToAuthenticated('notification', payload)\n sent = 1\n break\n\n case 'role':\n if (target_value) {\n this.socket.emitToRole(target_value, 'notification', payload)\n sent = 1\n }\n break\n\n case 'users':\n if (target_value) {\n const userIds = this.safeJsonParse<string[]>(\n target_value,\n [],\n { target_value, context: 'send.users' }\n )\n for (const userId of userIds) {\n this.socket.emitToUser(userId, 'notification', payload)\n }\n sent = userIds.length\n }\n break\n\n case 'user':\n if (target_value) {\n this.socket.emitToUser(target_value, 'notification', payload)\n sent = this.socket.isUserConnected(target_value) ? 1 : 0\n }\n break\n }\n\n this.logger.debug({ id, target_type, sent }, 'Notification sent')\n }\n\n // 3. Emitir evento interno\n this.eventsNotify('notifications.sent', { id, target_type, target_value, sent })\n\n return { id, sent }\n }\n\n /**\n * Checks if a user has access to a notification based on its target\n */\n canUserAccess(notification: Notification, userId: string, roleIds?: string[]): boolean {\n switch (notification.target_type) {\n case 'all':\n case 'authenticated':\n return true\n case 'role':\n return roleIds ? roleIds.includes(notification.target_value || '') : false\n case 'users': {\n const userIds = this.safeJsonParse<string[]>(\n notification.target_value || '[]',\n [],\n { notificationId: notification.id, context: 'canUserAccess.users' }\n )\n return userIds.includes(userId)\n }\n case 'user':\n return notification.target_value === userId\n default:\n return false\n }\n }\n\n /**\n * Marks a notification as read by a user.\n * If roleIds is provided, verifies access before marking.\n * Uses onConflict to prevent duplicates atomically.\n */\n async markAsRead(notificationId: string, userId: string, roleIds?: string[]): Promise<boolean> {\n const notification = await this.findById(notificationId)\n if (!notification) return false\n\n // Access check when roleIds provided (Socket.IO path)\n if (roleIds !== undefined && !this.canUserAccess(notification, userId, roleIds)) {\n return false\n }\n\n await this.db(this.t('notification_reads'))\n .insert({\n id: this.generateId(),\n notification_id: notificationId,\n user_id: userId,\n read_at: this.nowTimestamp(this.db)\n })\n .onConflict(['notification_id', 'user_id'])\n .ignore()\n\n // Emit Socket.IO for tab sync\n if (this.socket.isInitialized()) {\n this.socket.emitToUser(userId, 'notification:read', { notificationId })\n }\n\n this.logger.debug({ notificationId, userId }, 'Notification marked as read')\n return true\n }\n\n /**\n * Marks all unread notifications as read by a user.\n * Batches DB operations for efficiency.\n */\n async markAllAsRead(userId: string, roleIds?: string[], userCreatedAt?: Date | string): Promise<number> {\n const unread = await this.getUnread(userId, roleIds, userCreatedAt)\n if (unread.length === 0) return 0\n\n const now = this.nowTimestamp(this.db)\n\n // Batch insert into notification_reads (UNIQUE constraint prevents duplicates)\n const reads = unread.map(n => ({\n id: this.generateId(),\n notification_id: n.id,\n user_id: userId,\n read_at: now\n }))\n\n // Batch insert with conflict handling — idempotent under concurrent calls\n await this.db.transaction(async (trx) => {\n for (const read of reads) {\n await trx(this.t('notification_reads'))\n .insert(read)\n .onConflict(['notification_id', 'user_id'])\n .ignore()\n }\n })\n\n // Single batch event for tab sync (not N individual events)\n if (this.socket.isInitialized()) {\n this.socket.emitToUser(userId, 'notification:read', {\n notificationIds: unread.map(n => n.id)\n })\n }\n\n this.logger.debug({ userId, count: unread.length }, 'All notifications marked as read')\n return unread.length\n }\n\n /**\n * Gets unread notifications for a user.\n * Only returns notifications created after the user's registration date.\n * Uses LEFT JOIN with notification_reads to determine read status.\n */\n async getUnread(userId: string, roleIds?: string[], userCreatedAt?: Date | string): Promise<Notification[]> {\n const now = this.nowTimestamp(this.db)\n\n const db = this.db\n const t = this.t\n const query = db(t('notifications'))\n .leftJoin(t('notification_reads'), function () {\n this.on(`${t('notifications')}.id`, '=', `${t('notification_reads')}.notification_id`)\n .andOn(`${t('notification_reads')}.user_id`, '=', db.raw('?', [userId]))\n })\n .whereNull(`${t('notification_reads')}.id`)\n .where(function () {\n this.whereNull(`${t('notifications')}.expires_at`).orWhere(`${t('notifications')}.expires_at`, '>', now)\n })\n .select(`${t('notifications')}.*`)\n .orderBy(`${t('notifications')}.created_at`, 'desc')\n\n // Filter out notifications created before the user existed\n if (userCreatedAt) {\n query.andWhere(`${t('notifications')}.created_at`, '>=', userCreatedAt)\n }\n\n const notifications = await query\n\n // Filter by target access in memory\n return notifications.filter((n: Notification) => this.canUserAccess(n, userId, roleIds))\n }\n\n /**\n * Gets a notification by ID\n */\n async findById(id: string): Promise<Notification | null> {\n const notification = await this.db(this.t('notifications')).where('id', id).first()\n return notification || null\n }\n\n /**\n * Deletes a notification\n */\n async delete(id: string): Promise<boolean> {\n const deleted = await this.db(this.t('notifications')).where('id', id).delete()\n return deleted > 0\n }\n\n /**\n * Cleans up expired notifications\n */\n async cleanupExpired(): Promise<number> {\n const deleted = await this.db(this.t('notifications'))\n .where('expires_at', '<', this.nowTimestamp(this.db))\n .whereNotNull('expires_at')\n .delete()\n\n if (deleted > 0) {\n this.logger.info({ deleted }, 'Cleaned up expired notifications')\n }\n\n return deleted\n }\n}\n\nlet serviceInstance: NotificationService | null = null\n\n/**\n * Initializes the notifications service\n */\nexport function initNotificationService(ctx: ModuleContext): NotificationService {\n serviceInstance = new NotificationService(ctx)\n return serviceInstance\n}\n\n/**\n * Gets the service instance\n */\nexport function getNotificationService(): NotificationService {\n if (!serviceInstance) {\n throw new Error('NotificationService not initialized')\n }\n return serviceInstance\n}\n","import { z } from 'zod'\n\n// ============================================================================\n// TYPE ALIASES\n// ============================================================================\n\n/**\n * Notification types\n */\nexport type NotificationType = 'info' | 'warning' | 'error' | 'success'\n\n/**\n * Notification priority\n */\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent'\n\n/**\n * Target types for notifications\n */\nexport type NotificationTarget = 'all' | 'authenticated' | 'role' | 'users' | 'user'\n\n// ============================================================================\n// SHARED OPTIONS (DRY)\n// ============================================================================\n\n/**\n * Notification type options for select fields\n */\nexport const NOTIFICATION_TYPE_OPTIONS = [\n { value: 'info', label: { en: 'Info', es: 'Información' } },\n { value: 'success', label: { en: 'Success', es: 'Éxito' } },\n { value: 'warning', label: { en: 'Warning', es: 'Advertencia' } },\n { value: 'error', label: { en: 'Error', es: 'Error' } }\n] as const\n\n/**\n * Notification priority options for select fields\n */\nexport const NOTIFICATION_PRIORITY_OPTIONS = [\n { value: 'low', label: { en: 'Low', es: 'Baja' } },\n { value: 'normal', label: { en: 'Normal', es: 'Normal' } },\n { value: 'high', label: { en: 'High', es: 'Alta' } },\n { value: 'urgent', label: { en: 'Urgent', es: 'Urgente' } }\n] as const\n\n/**\n * Notification target type options for select fields\n */\nexport const NOTIFICATION_TARGET_OPTIONS = [\n { value: 'user', label: { en: 'User', es: 'Usuario' } },\n { value: 'role', label: { en: 'Role', es: 'Rol' } },\n { value: 'all', label: { en: 'All users', es: 'Todos los usuarios' } }\n] as const\n\n// ============================================================================\n// ZOD SCHEMAS\n// ============================================================================\n\n/**\n * Input schema for sending notifications\n */\n// Preprocess: convert empty strings to undefined for optional fields\nconst emptyToUndefined = z.preprocess((v) => (v === '' ? undefined : v), z.any())\n\nexport const sendNotificationInputSchema = z.object({\n title: z.string().min(1, 'Title is required'),\n message: z.string().min(1, 'Message is required'),\n type: emptyToUndefined.pipe(z.enum(['info', 'warning', 'error', 'success']).optional().default('info')),\n priority: emptyToUndefined.pipe(z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal')),\n target_type: z.enum(['all', 'authenticated', 'role', 'users', 'user']),\n target_value: z.string().optional(),\n link: z.string().url().optional().or(z.literal('')),\n expires_at: emptyToUndefined.pipe(z.string().datetime().optional())\n}).refine(\n (data) => {\n if (['role', 'user', 'users'].includes(data.target_type)) {\n return !!data.target_value\n }\n return true\n },\n { message: 'target_value is required for role, user, and users target types', path: ['target_value'] }\n)\n\nexport type SendNotificationInput = z.output<typeof sendNotificationInputSchema>\n\n\n// ============================================================================\n// ENTITY TYPES\n// ============================================================================\n\n/**\n * Notification in the DB\n */\nexport interface Notification {\n id: string\n title: string\n message: string\n type: NotificationType\n priority: NotificationPriority\n target_type: NotificationTarget\n target_value: string | null\n link: string | null\n expires_at: Date | null\n created_at: Date\n}\n\n/**\n * Payload sent over Socket.IO\n */\nexport interface NotificationPayload {\n id: string\n title: string\n message: string\n type: NotificationType\n priority: NotificationPriority\n link?: string\n timestamp: string\n}\n","import type { Request, Response, ModuleContext, Router, AuthRequest, BaseUsersService } from '@gzl10/nexus-sdk'\nimport { getNotificationService } from './notifications.service.js'\n\n/**\n * Notifications Routes\n *\n * Auto-mounted from definitions:\n * - sendNotificationAction (action) -> POST /notifications/send\n * - notificationEntity.actions[delete] (row action) -> DELETE /notifications/delete/:id\n *\n * Manual routes defined here:\n * - GET /notifications (admin list)\n * - GET /notifications/unread (user's unread)\n * - POST /notifications/:id/read (mark as read)\n * - POST /notifications/read-all (mark all as read)\n * - POST /notifications/cleanup (admin cleanup)\n */\nexport function createNotificationsRoutes(ctx: ModuleContext): Router {\n const router = ctx.createRouter()\n const { auth } = ctx.core.middleware\n const { UnauthorizedError, ForbiddenError, NotFoundError, ValidationError } = ctx.core.errors\n const usersService = ctx.services.get<BaseUsersService>('users')\n\n if (!auth) {\n throw new Error('Auth middleware not found. Ensure auth module loads before notifications.')\n }\n\n /**\n * GET /notifications/unread\n * Gets unread notifications for the authenticated user\n */\n router.get('/unread', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const service = getNotificationService()\n const roleIds = await usersService.getRoleIds(user.id)\n const notifications = await service.getUnread(user.id, roleIds, user.created_at)\n\n res.json({\n items: notifications,\n total: notifications.length,\n page: 1,\n limit: notifications.length,\n totalPages: 1,\n hasNext: false\n })\n })\n\n /**\n * POST /notifications/:id/read\n * Marks a notification as read (validates user has access)\n */\n router.post('/:id/read', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const id = req.params['id']\n if (!id) throw new ValidationError('VALIDATION_ERROR')\n\n const service = getNotificationService()\n\n // Verify notification exists and user has access\n const notification = await service.findById(id)\n if (!notification) throw new NotFoundError('RESOURCE_NOT_FOUND')\n\n const roleIds = await usersService.getRoleIds(user.id)\n if (!service.canUserAccess(notification, user.id, roleIds)) throw new ForbiddenError('FORBIDDEN')\n\n await service.markAsRead(id, user.id)\n res.status(204).send()\n })\n\n /**\n * POST /notifications/read-all\n * Marks all notifications as read\n */\n router.post('/read-all', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const service = getNotificationService()\n const roleIds = await usersService.getRoleIds(user.id)\n const count = await service.markAllAsRead(user.id, roleIds, user.created_at)\n\n res.json({ count })\n })\n\n /**\n * GET /notifications\n * Lists all notifications (admin only)\n * Note: Manual route because event entity auto-mount uses different path\n */\n router.get('/', auth, async (req: Request, res: Response) => {\n const ability = (req as AuthRequest).ability\n if (!ability?.can('manage', 'all')) throw new ForbiddenError('FORBIDDEN')\n\n const limit = Math.min(Math.max(parseInt(req.query['limit'] as string, 10) || 50, 1), 200)\n const page = Math.max(parseInt(req.query['page'] as string, 10) || 1, 1)\n const offset = (page - 1) * limit\n\n const notifications = await ctx.db.knex(ctx.db.t('notifications'))\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset)\n\n const countResult = await ctx.db.knex(ctx.db.t('notifications')).count('* as count').first<{ count: string | number }>()\n const total = parseInt(String(countResult?.count || 0))\n const totalPages = Math.ceil(total / limit)\n\n res.json({\n items: notifications,\n total,\n page,\n limit,\n totalPages,\n hasNext: page < totalPages\n })\n })\n\n /**\n * POST /notifications/cleanup\n * Cleans up expired notifications (admin only)\n */\n const cleanupRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1000, max: 1, message: 'Cleanup solo puede ejecutarse 1 vez por minuto' })\n router.post('/cleanup', cleanupRateLimit, auth, async (req: Request, res: Response) => {\n const ability = (req as AuthRequest).ability\n if (!ability?.can('manage', 'all')) throw new ForbiddenError('FORBIDDEN')\n\n const service = getNotificationService()\n const deleted = await service.cleanupExpired()\n\n res.json({ deleted })\n })\n\n return router\n}\n","import type { ModuleContext } from '@gzl10/nexus-sdk'\nimport { getNotificationService } from './notifications.service.js'\n\ninterface SocketLike {\n data: { userId?: string; roleIds?: string[]; authenticated?: boolean }\n on(event: string, handler: (...args: unknown[]) => void): void\n}\n\n/**\n * Registers Socket.IO handlers for notifications\n */\nexport function registerNotificationSocketHandlers(ctx: ModuleContext): void {\n if (!ctx.core.socket.isInitialized()) {\n ctx.core.logger.debug('Socket.IO not initialized, skipping notification handlers')\n return\n }\n\n const io = ctx.core.socket.getIO() as unknown as { on(event: string, handler: (socket: SocketLike) => void): void }\n\n io.on('connection', (socket: SocketLike) => {\n const { userId, roleIds, authenticated } = socket.data\n\n if (!authenticated || !userId) {\n return\n }\n\n /**\n * notification:read - Mark notification as read (with access check)\n */\n socket.on('notification:read', async (data: { notificationId: string }, callback?: (res: { success: boolean; error?: string }) => void) => {\n try {\n if (!data?.notificationId || typeof data.notificationId !== 'string') {\n callback?.({ success: false, error: 'INVALID_INPUT' })\n return\n }\n\n const service = getNotificationService()\n // roleIds passed → service verifies access internally (single query)\n const success = await service.markAsRead(data.notificationId, userId, roleIds)\n if (!success) {\n callback?.({ success: false, error: 'NOT_FOUND' })\n return\n }\n callback?.({ success: true })\n } catch (err) {\n ctx.core.logger.error({ err, userId, notificationId: data?.notificationId }, 'Error marking notification as read')\n callback?.({ success: false, error: 'INTERNAL_ERROR' })\n }\n })\n\n /**\n * notifications:read-all - Mark all as read\n */\n socket.on('notifications:read-all', async (callback?: (res: { success: boolean; count: number; error?: string }) => void) => {\n try {\n const service = getNotificationService()\n const user = await ctx.db.knex('users').where('id', userId).select('created_at').first()\n\n if (!user) {\n callback?.({ success: false, count: 0, error: 'USER_NOT_FOUND' })\n return\n }\n\n const count = await service.markAllAsRead(userId, roleIds ?? [], user.created_at)\n callback?.({ success: true, count })\n } catch (err) {\n ctx.core.logger.error({ err, userId }, 'Error marking all notifications as read')\n callback?.({ success: false, count: 0, error: 'INTERNAL_ERROR' })\n }\n })\n })\n\n ctx.core.logger.debug('Notification socket handlers registered')\n}\n","/**\n * @module notifications\n * @description Real-time notifications with Socket.IO delivery and persistence\n */\n\nimport type { ModuleManifest } from '@gzl10/nexus-sdk'\nimport {\n notificationEntity,\n notificationReadEntity,\n sendNotificationAction\n} from './notifications.entity.js'\nimport { createNotificationsRoutes } from './notifications.routes.js'\nimport { initNotificationService, getNotificationService, NotificationService } from './notifications.service.js'\nimport { registerNotificationSocketHandlers } from './notifications.socket.js'\n\nexport {\n NotificationService,\n getNotificationService,\n initNotificationService\n}\n\n// Re-export all types from the centralized types file\nexport type {\n NotificationType,\n NotificationPriority,\n NotificationTarget,\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\nexport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\n\n/**\n * Real-time notifications module\n */\nexport const notificationsModule: ModuleManifest = {\n name: 'notifications',\n label: { en: 'Notifications', es: 'Notificaciones' },\n icon: 'mdi:bell-outline',\n description: { en: 'Real-time notifications via Socket.IO', es: 'Notificaciones en tiempo real vía Socket.IO' },\n category: 'messaging',\n definitions: [\n notificationEntity,\n notificationReadEntity\n ],\n\n actions: [sendNotificationAction],\n\n routePrefix: '/notifications',\n routes: createNotificationsRoutes,\n\n init: (ctx) => {\n const service = initNotificationService(ctx)\n ctx.services.register('notifications', service)\n\n // Registrar handlers Socket.IO cuando esté listo\n ctx.core.socket.onReady(() => registerNotificationSocketHandlers(ctx))\n\n ctx.core.logger.debug('Notifications module initialized')\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/notifications.entity.ts","../src/notifications.service.ts","../src/notifications.types.ts","../src/notifications.routes.ts","../src/notifications.socket.ts","../src/notifications-module.ts"],"sourcesContent":["/**\n * @module @gzl10/nexus-plugin-notifications\n * @description Real-time in-app notifications via Socket.IO\n */\n\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { PluginManifest } from '@gzl10/nexus-sdk'\nimport { notificationsModule } from './notifications-module.js'\n\nconst pkg = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'package.json'), 'utf-8')) as { name: string; version: string }\n\nexport { NotificationService, getNotificationService, initNotificationService } from './notifications.service.js'\n\nexport type {\n NotificationType,\n NotificationPriority,\n NotificationTarget,\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\nexport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\n\nexport { notificationEntity, notificationReadEntity } from './notifications.entity.js'\nexport { notificationsModule } from './notifications-module.js'\n\nexport const notificationsPlugin: PluginManifest = {\n name: pkg.name,\n code: 'ntf',\n label: { en: 'Notifications', es: 'Notificaciones' },\n icon: 'mdi:bell-outline',\n category: 'messaging',\n version: pkg.version,\n description: {\n en: 'Real-time in-app notifications via Socket.IO',\n es: 'Notificaciones in-app en tiempo real vía Socket.IO'\n },\n modules: [notificationsModule]\n}\n\nexport default notificationsPlugin\n","import type { EventEntityDefinition, ActionDefinition } from '@gzl10/nexus-sdk'\nimport { useIdField, useTextField, useTextareaField, useSelectField, useUrlField, useDatetimeField, useExpiresAtField } from '@gzl10/nexus-sdk/fields'\nimport { getNotificationService } from './notifications.service.js'\nimport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\nimport type { SendNotificationInput } from './notifications.types.js'\n\n/**\n * Notification (event) - ephemeral notifications with TTL\n */\nexport const notificationEntity: EventEntityDefinition = {\n type: 'event',\n realtime: 'sync',\n immutable: true,\n table: 'notifications',\n routePrefix: '/',\n label: { en: 'Notification', es: 'Notificación' },\n labelPlural: { en: 'Notifications', es: 'Notificaciones' },\n labelField: 'title',\n timestamps: true,\n retention: { days: 30 },\n calendarFrom: 'expires_at',\n fields: {\n id: useIdField(),\n title: useTextField({ label: { en: 'Title', es: 'Título' }, required: true }),\n message: useTextareaField({ label: { en: 'Message', es: 'Mensaje' }, required: true }),\n type: useSelectField({\n label: { en: 'Type', es: 'Tipo' },\n options: [...NOTIFICATION_TYPE_OPTIONS],\n defaultValue: 'info',\n required: true,\n size: 20\n }),\n priority: useSelectField({\n label: { en: 'Priority', es: 'Prioridad' },\n options: [...NOTIFICATION_PRIORITY_OPTIONS],\n defaultValue: 'normal',\n required: true,\n size: 10\n }),\n target_type: useSelectField({\n label: { en: 'Target Type', es: 'Tipo de destino' },\n options: [...NOTIFICATION_TARGET_OPTIONS],\n required: true,\n size: 20\n }),\n target_value: useTextField({ label: { en: 'Target Value', es: 'Valor del destino' } }),\n link: useUrlField({ label: { en: 'Link', es: 'Enlace' } }),\n expires_at: useExpiresAtField()\n },\n actions: [\n {\n key: 'delete',\n scope: 'row',\n icon: 'mdi:delete-outline',\n label: { en: 'Delete Notification', es: 'Eliminar notificación' },\n method: 'DELETE',\n handler: async (_ctx, input: unknown) => {\n const record = (input as Record<string, unknown>)?._record as Record<string, unknown> | undefined\n const id = typeof record?.id === 'string' ? record.id : undefined\n if (!id) return { deleted: false }\n const service = getNotificationService()\n const deleted = await service.delete(id)\n return { deleted }\n },\n casl: {\n subject: 'Notification',\n permissions: {\n ADMIN: { actions: ['execute'] }\n }\n }\n } satisfies ActionDefinition\n ],\n casl: {\n subject: 'Notification',\n permissions: {\n MANAGER: { actions: ['read'] },\n EDITOR: { actions: ['read'] },\n CONTRIBUTOR: { actions: ['read'] },\n USER: [\n { actions: ['read'], conditions: { target_type: 'user', target_value: '${user.id}' } },\n { actions: ['read'], conditions: { target_type: 'all' } },\n { actions: ['read'], conditions: { target_type: 'authenticated' } },\n ],\n VIEWER: [\n { actions: ['read'], conditions: { target_type: 'user', target_value: '${user.id}' } },\n { actions: ['read'], conditions: { target_type: 'all' } },\n { actions: ['read'], conditions: { target_type: 'authenticated' } },\n ],\n SUPPORT: { actions: ['read'] }\n }\n }\n}\n\n/**\n * NotificationRead (event) - read log\n */\nexport const notificationReadEntity: EventEntityDefinition = {\n type: 'event',\n immutable: true,\n expose: false,\n table: 'notification_reads',\n label: { en: 'Notification Read', es: 'Lectura de notificación' },\n labelPlural: { en: 'Notification Reads', es: 'Lecturas de notificaciones' },\n labelField: 'notification_id',\n retention: { days: 90 },\n fields: {\n id: useIdField(),\n notification_id: useTextField({ label: { en: 'Notification ID', es: 'ID de notificación' }, required: true, size: 26, hidden: true }),\n user_id: useTextField({ label: { en: 'User ID', es: 'ID de usuario' }, required: true, size: 26, hidden: true }),\n read_at: useDatetimeField({ label: { en: 'Read At', es: 'Leído el' }, required: true })\n },\n indexes: [\n { columns: ['notification_id', 'user_id'], unique: true }\n ],\n casl: {\n subject: 'NotificationRead',\n permissions: {\n ADMIN: { actions: ['manage'] }\n }\n }\n}\n\n/**\n * SendNotification (action) - send notification\n */\nexport const sendNotificationAction: ActionDefinition = {\n key: \"send\",\n scope: \"module\",\n label: { en: \"Send Notification\", es: \"Enviar notificación\" },\n output: {},\n inputSchema: sendNotificationInputSchema,\n handler: async (_ctx, input: unknown) => {\n // Usar getNotificationService() directamente, no ctx.services\n // porque createModuleRouters() sobrescribe ctx.services['notifications']\n // con el EventEntityService de notificationEntity\n const service = getNotificationService();\n return service.send(input as SendNotificationInput);\n },\n input: {\n title: useTextField({ label: { en: 'Title', es: 'Título' }, required: true }),\n message: useTextareaField({ label: { en: 'Message', es: 'Mensaje' }, required: true }),\n target_type: useSelectField({\n label: { en: 'Target Type', es: 'Tipo de destino' },\n options: [...NOTIFICATION_TARGET_OPTIONS],\n required: true,\n }),\n target_value: useTextField({ label: { en: 'Target Value', es: 'Valor del destino' } }),\n type: useSelectField({\n label: { en: 'Type', es: 'Tipo' },\n options: [...NOTIFICATION_TYPE_OPTIONS],\n }),\n priority: useSelectField({\n label: { en: 'Priority', es: 'Prioridad' },\n options: [...NOTIFICATION_PRIORITY_OPTIONS],\n }),\n link: useUrlField({ label: { en: 'Link', es: 'Enlace' } }),\n expires_at: useDatetimeField({ label: { en: 'Expires At', es: 'Expira el' } }),\n },\n casl: {\n subject: \"Notification\",\n permissions: {\n ADMIN: { actions: [\"execute\"] },\n },\n },\n};\n","import type { ModuleContext } from '@gzl10/nexus-sdk'\nimport type {\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\n/**\n * Notifications service\n */\nexport class NotificationService {\n private db: ModuleContext['db']['knex']\n private logger: ModuleContext['core']['logger']\n private generateId: () => string\n private nowTimestamp: (db: ModuleContext['db']['knex']) => string\n private formatTimestamp: (db: ModuleContext['db']['knex'], date?: Date) => string\n private safeJsonParse: ModuleContext['core']['safeJsonParse']\n private socket: ModuleContext['core']['socket']\n private eventsNotify: ModuleContext['events']['notify']\n private t: (name: string) => string\n\n constructor(ctx: ModuleContext) {\n this.db = ctx.db.knex\n this.logger = ctx.core.logger.child({ service: 'notifications' })\n this.generateId = ctx.core.generateId\n this.nowTimestamp = ctx.db.nowTimestamp\n this.formatTimestamp = ctx.db.formatTimestamp\n this.safeJsonParse = ctx.core.safeJsonParse\n this.socket = ctx.core.socket\n this.eventsNotify = ctx.events.notify.bind(ctx.events)\n this.t = ctx.db.t\n }\n\n /**\n * Sends a notification\n */\n async send(options: SendNotificationInput): Promise<{ id: string; sent: number }> {\n const { target_type, target_value } = options\n let sent = 0\n\n const type = options.type || 'info'\n const priority = options.priority || 'normal'\n\n // 1. Persistir notificación\n const id = this.generateId()\n const now = this.nowTimestamp(this.db)\n\n await this.db(this.t('notifications')).insert({\n id,\n title: options.title,\n message: options.message,\n type,\n priority,\n target_type,\n target_value: target_value || null,\n link: options.link || null,\n expires_at: options.expires_at ? this.formatTimestamp(this.db, new Date(options.expires_at)) : null,\n created_at: now\n })\n\n // 2. Emitir por Socket.IO si está inicializado (via ctx.core.socket helpers)\n if (this.socket.isInitialized()) {\n const payload: NotificationPayload = {\n id,\n title: options.title,\n message: options.message,\n type,\n priority,\n link: options.link || undefined,\n timestamp: now\n }\n\n switch (target_type) {\n case 'all':\n this.socket.emitToAll('notification', payload)\n sent = 1 // broadcast, exact count not critical\n break\n\n case 'authenticated':\n this.socket.emitToAuthenticated('notification', payload)\n sent = 1\n break\n\n case 'role':\n if (target_value) {\n this.socket.emitToRole(target_value, 'notification', payload)\n sent = 1\n }\n break\n\n case 'users':\n if (target_value) {\n const userIds = this.safeJsonParse<string[]>(\n target_value,\n [],\n { target_value, context: 'send.users' }\n )\n for (const userId of userIds) {\n this.socket.emitToUser(userId, 'notification', payload)\n }\n sent = userIds.length\n }\n break\n\n case 'user':\n if (target_value) {\n this.socket.emitToUser(target_value, 'notification', payload)\n sent = this.socket.isUserConnected(target_value) ? 1 : 0\n }\n break\n }\n\n this.logger.debug({ id, target_type, sent }, 'Notification sent')\n }\n\n // 3. Emitir evento interno\n this.eventsNotify('notifications.sent', { id, target_type, target_value, sent })\n\n return { id, sent }\n }\n\n /**\n * Checks if a user has access to a notification based on its target\n */\n canUserAccess(notification: Notification, userId: string, roleIds?: string[]): boolean {\n switch (notification.target_type) {\n case 'all':\n case 'authenticated':\n return true\n case 'role':\n return roleIds ? roleIds.includes(notification.target_value || '') : false\n case 'users': {\n const userIds = this.safeJsonParse<string[]>(\n notification.target_value || '[]',\n [],\n { notificationId: notification.id, context: 'canUserAccess.users' }\n )\n return userIds.includes(userId)\n }\n case 'user':\n return notification.target_value === userId\n default:\n return false\n }\n }\n\n /**\n * Marks a notification as read by a user.\n * If roleIds is provided, verifies access before marking.\n * Uses onConflict to prevent duplicates atomically.\n */\n async markAsRead(notificationId: string, userId: string, roleIds?: string[]): Promise<boolean> {\n const notification = await this.findById(notificationId)\n if (!notification) return false\n\n // Access check when roleIds provided (Socket.IO path)\n if (roleIds !== undefined && !this.canUserAccess(notification, userId, roleIds)) {\n return false\n }\n\n await this.db(this.t('notification_reads'))\n .insert({\n id: this.generateId(),\n notification_id: notificationId,\n user_id: userId,\n read_at: this.nowTimestamp(this.db)\n })\n .onConflict(['notification_id', 'user_id'])\n .ignore()\n\n // Emit Socket.IO for tab sync\n if (this.socket.isInitialized()) {\n this.socket.emitToUser(userId, 'notification:read', { notificationId })\n }\n\n this.logger.debug({ notificationId, userId }, 'Notification marked as read')\n return true\n }\n\n /**\n * Marks all unread notifications as read by a user.\n * Batches DB operations for efficiency.\n */\n async markAllAsRead(userId: string, roleIds?: string[], userCreatedAt?: Date | string): Promise<number> {\n const unread = await this.getUnread(userId, roleIds, userCreatedAt)\n if (unread.length === 0) return 0\n\n const now = this.nowTimestamp(this.db)\n\n // Batch insert into notification_reads (UNIQUE constraint prevents duplicates)\n const reads = unread.map(n => ({\n id: this.generateId(),\n notification_id: n.id,\n user_id: userId,\n read_at: now\n }))\n\n // Batch insert with conflict handling — idempotent under concurrent calls\n await this.db.transaction(async (trx) => {\n for (const read of reads) {\n await trx(this.t('notification_reads'))\n .insert(read)\n .onConflict(['notification_id', 'user_id'])\n .ignore()\n }\n })\n\n // Single batch event for tab sync (not N individual events)\n if (this.socket.isInitialized()) {\n this.socket.emitToUser(userId, 'notification:read', {\n notificationIds: unread.map(n => n.id)\n })\n }\n\n this.logger.debug({ userId, count: unread.length }, 'All notifications marked as read')\n return unread.length\n }\n\n /**\n * Gets unread notifications for a user.\n * Only returns notifications created after the user's registration date.\n * Uses LEFT JOIN with notification_reads to determine read status.\n */\n async getUnread(userId: string, roleIds?: string[], userCreatedAt?: Date | string): Promise<Notification[]> {\n const now = this.nowTimestamp(this.db)\n\n const db = this.db\n const t = this.t\n const query = db(t('notifications'))\n .leftJoin(t('notification_reads'), function () {\n this.on(`${t('notifications')}.id`, '=', `${t('notification_reads')}.notification_id`)\n .andOn(`${t('notification_reads')}.user_id`, '=', db.raw('?', [userId]))\n })\n .whereNull(`${t('notification_reads')}.id`)\n .where(function () {\n this.whereNull(`${t('notifications')}.expires_at`).orWhere(`${t('notifications')}.expires_at`, '>', now)\n })\n .select(`${t('notifications')}.*`)\n .orderBy(`${t('notifications')}.created_at`, 'desc')\n\n // Filter out notifications created before the user existed\n if (userCreatedAt) {\n query.andWhere(`${t('notifications')}.created_at`, '>=', userCreatedAt)\n }\n\n const notifications = await query\n\n // Filter by target access in memory\n return notifications.filter((n: Notification) => this.canUserAccess(n, userId, roleIds))\n }\n\n /**\n * Gets a notification by ID\n */\n async findById(id: string): Promise<Notification | null> {\n const notification = await this.db(this.t('notifications')).where('id', id).first()\n return notification || null\n }\n\n /**\n * Deletes a notification\n */\n async delete(id: string): Promise<boolean> {\n const deleted = await this.db(this.t('notifications')).where('id', id).delete()\n return deleted > 0\n }\n\n /**\n * Cleans up expired notifications\n */\n async cleanupExpired(): Promise<number> {\n const deleted = await this.db(this.t('notifications'))\n .where('expires_at', '<', this.nowTimestamp(this.db))\n .whereNotNull('expires_at')\n .delete()\n\n if (deleted > 0) {\n this.logger.info({ deleted }, 'Cleaned up expired notifications')\n }\n\n return deleted\n }\n}\n\nlet serviceInstance: NotificationService | null = null\n\n/**\n * Initializes the notifications service\n */\nexport function initNotificationService(ctx: ModuleContext): NotificationService {\n serviceInstance = new NotificationService(ctx)\n return serviceInstance\n}\n\n/**\n * Gets the service instance\n */\nexport function getNotificationService(): NotificationService {\n if (!serviceInstance) {\n throw new Error('NotificationService not initialized')\n }\n return serviceInstance\n}\n","import { z } from 'zod'\n\n// ============================================================================\n// TYPE ALIASES\n// ============================================================================\n\n/**\n * Notification types\n */\nexport type NotificationType = 'info' | 'warning' | 'error' | 'success'\n\n/**\n * Notification priority\n */\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent'\n\n/**\n * Target types for notifications\n */\nexport type NotificationTarget = 'all' | 'authenticated' | 'role' | 'users' | 'user'\n\n// ============================================================================\n// SHARED OPTIONS (DRY)\n// ============================================================================\n\n/**\n * Notification type options for select fields\n */\nexport const NOTIFICATION_TYPE_OPTIONS = [\n { value: 'info', label: { en: 'Info', es: 'Información' } },\n { value: 'success', label: { en: 'Success', es: 'Éxito' } },\n { value: 'warning', label: { en: 'Warning', es: 'Advertencia' } },\n { value: 'error', label: { en: 'Error', es: 'Error' } }\n] as const\n\n/**\n * Notification priority options for select fields\n */\nexport const NOTIFICATION_PRIORITY_OPTIONS = [\n { value: 'low', label: { en: 'Low', es: 'Baja' } },\n { value: 'normal', label: { en: 'Normal', es: 'Normal' } },\n { value: 'high', label: { en: 'High', es: 'Alta' } },\n { value: 'urgent', label: { en: 'Urgent', es: 'Urgente' } }\n] as const\n\n/**\n * Notification target type options for select fields\n */\nexport const NOTIFICATION_TARGET_OPTIONS = [\n { value: 'user', label: { en: 'User', es: 'Usuario' } },\n { value: 'role', label: { en: 'Role', es: 'Rol' } },\n { value: 'all', label: { en: 'All users', es: 'Todos los usuarios' } }\n] as const\n\n// ============================================================================\n// ZOD SCHEMAS\n// ============================================================================\n\n/**\n * Input schema for sending notifications\n */\n// Preprocess: convert empty strings to undefined for optional fields\nconst emptyToUndefined = z.preprocess((v) => (v === '' ? undefined : v), z.any())\n\nexport const sendNotificationInputSchema = z.object({\n title: z.string().min(1, 'Title is required'),\n message: z.string().min(1, 'Message is required'),\n type: emptyToUndefined.pipe(z.enum(['info', 'warning', 'error', 'success']).optional().default('info')),\n priority: emptyToUndefined.pipe(z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal')),\n target_type: z.enum(['all', 'authenticated', 'role', 'users', 'user']),\n target_value: z.string().optional(),\n link: z.string().url().optional().or(z.literal('')),\n expires_at: emptyToUndefined.pipe(z.string().datetime().optional())\n}).refine(\n (data) => {\n if (['role', 'user', 'users'].includes(data.target_type)) {\n return !!data.target_value\n }\n return true\n },\n { message: 'target_value is required for role, user, and users target types', path: ['target_value'] }\n)\n\nexport type SendNotificationInput = z.output<typeof sendNotificationInputSchema>\n\n\n// ============================================================================\n// ENTITY TYPES\n// ============================================================================\n\n/**\n * Notification in the DB\n */\nexport interface Notification {\n id: string\n title: string\n message: string\n type: NotificationType\n priority: NotificationPriority\n target_type: NotificationTarget\n target_value: string | null\n link: string | null\n expires_at: Date | null\n created_at: Date\n}\n\n/**\n * Payload sent over Socket.IO\n */\nexport interface NotificationPayload {\n id: string\n title: string\n message: string\n type: NotificationType\n priority: NotificationPriority\n link?: string\n timestamp: string\n}\n","import type { Request, Response, ModuleContext, Router, AuthRequest, BaseUsersService } from '@gzl10/nexus-sdk'\nimport { getNotificationService } from './notifications.service.js'\n\n/**\n * Notifications Routes\n *\n * Auto-mounted from definitions:\n * - sendNotificationAction (action) -> POST /notifications/send\n * - notificationEntity.actions[delete] (row action) -> DELETE /notifications/delete/:id\n *\n * Manual routes defined here:\n * - GET /notifications (admin list)\n * - GET /notifications/unread (user's unread)\n * - POST /notifications/:id/read (mark as read)\n * - POST /notifications/read-all (mark all as read)\n * - POST /notifications/cleanup (admin cleanup)\n */\nexport function createNotificationsRoutes(ctx: ModuleContext): Router {\n const router = ctx.createRouter()\n const { auth } = ctx.core.middleware\n const { UnauthorizedError, ForbiddenError, NotFoundError, ValidationError } = ctx.core.errors\n const usersService = ctx.services.get<BaseUsersService>('users')\n\n if (!auth) {\n throw new Error('Auth middleware not found. Ensure auth module loads before notifications.')\n }\n\n /**\n * GET /notifications/unread\n * Gets unread notifications for the authenticated user\n */\n router.get('/unread', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const service = getNotificationService()\n const roleIds = await usersService.getRoleIds(user.id)\n const notifications = await service.getUnread(user.id, roleIds, user.created_at)\n\n res.json({\n items: notifications,\n total: notifications.length,\n page: 1,\n limit: notifications.length,\n totalPages: 1,\n hasNext: false\n })\n })\n\n /**\n * POST /notifications/:id/read\n * Marks a notification as read (validates user has access)\n */\n router.post('/:id/read', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const id = req.params['id']\n if (!id) throw new ValidationError('VALIDATION_ERROR')\n\n const service = getNotificationService()\n\n // Verify notification exists and user has access\n const notification = await service.findById(id)\n if (!notification) throw new NotFoundError('RESOURCE_NOT_FOUND')\n\n const roleIds = await usersService.getRoleIds(user.id)\n if (!service.canUserAccess(notification, user.id, roleIds)) throw new ForbiddenError('FORBIDDEN')\n\n await service.markAsRead(id, user.id)\n res.status(204).send()\n })\n\n /**\n * POST /notifications/read-all\n * Marks all notifications as read\n */\n router.post('/read-all', auth, async (req: Request, res: Response) => {\n const user = (req as AuthRequest).user\n if (!user) throw new UnauthorizedError('AUTH_REQUIRED')\n\n const service = getNotificationService()\n const roleIds = await usersService.getRoleIds(user.id)\n const count = await service.markAllAsRead(user.id, roleIds, user.created_at)\n\n res.json({ count })\n })\n\n /**\n * GET /notifications\n * Lists all notifications (admin only)\n * Note: Manual route because event entity auto-mount uses different path\n */\n router.get('/', auth, async (req: Request, res: Response) => {\n const ability = (req as AuthRequest).ability\n if (!ability?.can('manage', 'all')) throw new ForbiddenError('FORBIDDEN')\n\n const limit = Math.min(Math.max(parseInt(req.query['limit'] as string, 10) || 50, 1), 200)\n const page = Math.max(parseInt(req.query['page'] as string, 10) || 1, 1)\n const offset = (page - 1) * limit\n\n const notifications = await ctx.db.knex(ctx.db.t('notifications'))\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset)\n\n const countResult = await ctx.db.knex(ctx.db.t('notifications')).count('* as count').first<{ count: string | number }>()\n const total = parseInt(String(countResult?.count || 0))\n const totalPages = Math.ceil(total / limit)\n\n res.json({\n items: notifications,\n total,\n page,\n limit,\n totalPages,\n hasNext: page < totalPages\n })\n })\n\n /**\n * POST /notifications/cleanup\n * Cleans up expired notifications (admin only)\n */\n const cleanupRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1000, max: 1, message: 'Cleanup solo puede ejecutarse 1 vez por minuto' })\n router.post('/cleanup', cleanupRateLimit, auth, async (req: Request, res: Response) => {\n const ability = (req as AuthRequest).ability\n if (!ability?.can('manage', 'all')) throw new ForbiddenError('FORBIDDEN')\n\n const service = getNotificationService()\n const deleted = await service.cleanupExpired()\n\n res.json({ deleted })\n })\n\n return router\n}\n","import type { ModuleContext } from '@gzl10/nexus-sdk'\nimport { getNotificationService } from './notifications.service.js'\n\ninterface SocketLike {\n data: { userId?: string; roleIds?: string[]; authenticated?: boolean }\n on(event: string, handler: (...args: unknown[]) => void): void\n}\n\n/**\n * Registers Socket.IO handlers for notifications\n */\nexport function registerNotificationSocketHandlers(ctx: ModuleContext): void {\n if (!ctx.core.socket.isInitialized()) {\n ctx.core.logger.debug('Socket.IO not initialized, skipping notification handlers')\n return\n }\n\n const io = ctx.core.socket.getIO() as unknown as { on(event: string, handler: (socket: SocketLike) => void): void }\n\n io.on('connection', (socket: SocketLike) => {\n const { userId, roleIds, authenticated } = socket.data\n\n if (!authenticated || !userId) {\n return\n }\n\n /**\n * notification:read - Mark notification as read (with access check)\n */\n socket.on('notification:read', async (data: { notificationId: string }, callback?: (res: { success: boolean; error?: string }) => void) => {\n try {\n if (!data?.notificationId || typeof data.notificationId !== 'string') {\n callback?.({ success: false, error: 'INVALID_INPUT' })\n return\n }\n\n const service = getNotificationService()\n // roleIds passed → service verifies access internally (single query)\n const success = await service.markAsRead(data.notificationId, userId, roleIds)\n if (!success) {\n callback?.({ success: false, error: 'NOT_FOUND' })\n return\n }\n callback?.({ success: true })\n } catch (err) {\n ctx.core.logger.error({ err, userId, notificationId: data?.notificationId }, 'Error marking notification as read')\n callback?.({ success: false, error: 'INTERNAL_ERROR' })\n }\n })\n\n /**\n * notifications:read-all - Mark all as read\n */\n socket.on('notifications:read-all', async (callback?: (res: { success: boolean; count: number; error?: string }) => void) => {\n try {\n const service = getNotificationService()\n const user = await ctx.db.knex('users').where('id', userId).select('created_at').first()\n\n if (!user) {\n callback?.({ success: false, count: 0, error: 'USER_NOT_FOUND' })\n return\n }\n\n const count = await service.markAllAsRead(userId, roleIds ?? [], user.created_at)\n callback?.({ success: true, count })\n } catch (err) {\n ctx.core.logger.error({ err, userId }, 'Error marking all notifications as read')\n callback?.({ success: false, count: 0, error: 'INTERNAL_ERROR' })\n }\n })\n })\n\n ctx.core.logger.debug('Notification socket handlers registered')\n}\n","/**\n * @module notifications\n * @description Real-time notifications with Socket.IO delivery and persistence\n */\n\nimport type { ModuleManifest } from '@gzl10/nexus-sdk'\nimport {\n notificationEntity,\n notificationReadEntity,\n sendNotificationAction\n} from './notifications.entity.js'\nimport { createNotificationsRoutes } from './notifications.routes.js'\nimport { initNotificationService, getNotificationService, NotificationService } from './notifications.service.js'\nimport { registerNotificationSocketHandlers } from './notifications.socket.js'\n\nexport {\n NotificationService,\n getNotificationService,\n initNotificationService\n}\n\n// Re-export all types from the centralized types file\nexport type {\n NotificationType,\n NotificationPriority,\n NotificationTarget,\n SendNotificationInput,\n Notification,\n NotificationPayload\n} from './notifications.types.js'\n\nexport {\n NOTIFICATION_TYPE_OPTIONS,\n NOTIFICATION_PRIORITY_OPTIONS,\n NOTIFICATION_TARGET_OPTIONS,\n sendNotificationInputSchema\n} from './notifications.types.js'\n\n/**\n * Real-time notifications module\n */\nexport const notificationsModule: ModuleManifest = {\n name: 'notifications',\n label: { en: 'Notifications', es: 'Notificaciones' },\n icon: 'mdi:bell-outline',\n description: { en: 'Real-time notifications via Socket.IO', es: 'Notificaciones en tiempo real vía Socket.IO' },\n category: 'messaging',\n definitions: [\n notificationEntity,\n notificationReadEntity\n ],\n\n actions: [sendNotificationAction],\n\n routePrefix: '/notifications',\n routes: createNotificationsRoutes,\n\n init: (ctx) => {\n const service = initNotificationService(ctx)\n ctx.services.register('notifications', service)\n\n // Registrar handlers Socket.IO cuando esté listo\n ctx.core.socket.onReady(() => registerNotificationSocketHandlers(ctx))\n\n ctx.core.logger.debug('Notifications module initialized')\n }\n}\n"],"mappings":"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","names":[]}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @gzl10/nexus-plugin-notifications
|
|
2
|
+
|
|
3
|
+
> Real-time notifications with Socket.IO delivery and read tracking.
|
|
4
|
+
|
|
5
|
+
## Entities
|
|
6
|
+
|
|
7
|
+
- **notifications** (event, immutable, realtime, 30-day retention): Notification records. Fields: title, message, type (info|warning|error|success), priority (low|normal|high|urgent), target_type (all|authenticated|role|users|user), target_value, link, expires_at.
|
|
8
|
+
- **notification_reads** (event, `expose: false`, 90-day retention): Read tracking. Fields: notification_id, user_id, read_at. Unique constraint: (notification_id, user_id).
|
|
9
|
+
|
|
10
|
+
## Service API
|
|
11
|
+
|
|
12
|
+
Register name: `notifications`
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const notifications = ctx.services.get<NotificationService>('notifications')
|
|
16
|
+
|
|
17
|
+
const { id, sent } = await notifications.send({
|
|
18
|
+
title: 'Update available',
|
|
19
|
+
message: 'New version released',
|
|
20
|
+
type: 'info',
|
|
21
|
+
priority: 'normal',
|
|
22
|
+
target_type: 'all', // all | authenticated | role | users | user
|
|
23
|
+
target_value?: 'admin-id', // role ID, user IDs array, or single user ID
|
|
24
|
+
link?: '/updates',
|
|
25
|
+
expires_at?: new Date(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const unread = await notifications.getUnread(userId, roleIds?, userCreatedAt?)
|
|
29
|
+
await notifications.markAsRead(notificationId, userId, roleIds?)
|
|
30
|
+
const count = await notifications.markAllAsRead(userId, roleIds?, userCreatedAt?)
|
|
31
|
+
const canAccess = notifications.canUserAccess(notification, userId, roleIds?)
|
|
32
|
+
await notifications.cleanupExpired()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Actions
|
|
36
|
+
|
|
37
|
+
- **send** (module scope, POST): Send notification. ADMIN only.
|
|
38
|
+
|
|
39
|
+
## Routes
|
|
40
|
+
|
|
41
|
+
- **GET /notifications/unread**: Get user's unread notifications.
|
|
42
|
+
- **POST /notifications/:id/read**: Mark single as read.
|
|
43
|
+
- **POST /notifications/read-all**: Mark all as read.
|
|
44
|
+
- **GET /notifications**: Admin list all (paginated).
|
|
45
|
+
- **POST /notifications/cleanup**: Remove expired (ADMIN, rate-limited 1/min).
|
|
46
|
+
|
|
47
|
+
## Socket.IO
|
|
48
|
+
|
|
49
|
+
- `notification:read` — Mark as read via socket.
|
|
50
|
+
- `notifications:read-all` — Mark all as read via socket.
|
|
51
|
+
- Server emits to targeted recipients (all, authenticated, role, users, user).
|
|
52
|
+
|
|
53
|
+
## CASL
|
|
54
|
+
|
|
55
|
+
- Notification: MANAGER/EDITOR/CONTRIBUTOR read. USER/VIEWER read (own or all/authenticated). ADMIN execute (delete).
|
|
56
|
+
- NotificationRead: ADMIN manage.
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
- Target types determine delivery: all (broadcast), authenticated, role, users (array), user (single).
|
|
61
|
+
- Read tracking with idempotent upsert (onConflict ignore).
|
|
62
|
+
- Filters out notifications created before user's registration date.
|
|
63
|
+
- Graceful degradation if Socket.IO not initialized.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gzl10/nexus-plugin-notifications",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nexus Plugin: Real-time in-app notifications via Socket.IO",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,28 +17,18 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"migrations",
|
|
20
|
-
"image.png"
|
|
20
|
+
"image.png",
|
|
21
|
+
"llms.txt"
|
|
21
22
|
],
|
|
22
23
|
"peerDependencies": {
|
|
23
|
-
"@gzl10/nexus-sdk": ">=0.14.0"
|
|
24
|
-
"knex": "^3.0.0",
|
|
25
|
-
"pino": "^9.0.0 || ^10.0.0",
|
|
26
|
-
"zod": "^3.0.0"
|
|
24
|
+
"@gzl10/nexus-sdk": ">=0.14.0"
|
|
27
25
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"optional": true
|
|
31
|
-
},
|
|
32
|
-
"pino": {
|
|
33
|
-
"optional": true
|
|
34
|
-
},
|
|
35
|
-
"zod": {
|
|
36
|
-
"optional": true
|
|
37
|
-
}
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"zod": "^3.24.0"
|
|
38
28
|
},
|
|
39
29
|
"devDependencies": {
|
|
40
|
-
"@gzl10/nexus-sdk": "0.
|
|
41
|
-
"@gzl10/nexus-client": "0.
|
|
30
|
+
"@gzl10/nexus-sdk": "0.17.0",
|
|
31
|
+
"@gzl10/nexus-client": "0.17.0"
|
|
42
32
|
},
|
|
43
33
|
"scripts": {
|
|
44
34
|
"build": "tsup",
|