@gzl10/nexus-plugin-notifications 0.17.0 → 0.19.0

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.js CHANGED
@@ -326,7 +326,7 @@ var notificationEntity = {
326
326
  return { deleted };
327
327
  },
328
328
  casl: {
329
- subject: "Notification",
329
+ subject: "NotificationItem",
330
330
  permissions: {
331
331
  ADMIN: { actions: ["execute"] }
332
332
  }
@@ -334,22 +334,14 @@ var notificationEntity = {
334
334
  }
335
335
  ],
336
336
  casl: {
337
- subject: "Notification",
337
+ subject: "NotificationItem",
338
338
  permissions: {
339
- MANAGER: { actions: ["read"] },
340
- EDITOR: { actions: ["read"] },
341
- CONTRIBUTOR: { actions: ["read"] },
342
- USER: [
339
+ "@": [
343
340
  { actions: ["read"], conditions: { target_type: "user", target_value: "${user.id}" } },
344
341
  { actions: ["read"], conditions: { target_type: "all" } },
345
342
  { actions: ["read"], conditions: { target_type: "authenticated" } }
346
343
  ],
347
- VIEWER: [
348
- { actions: ["read"], conditions: { target_type: "user", target_value: "${user.id}" } },
349
- { actions: ["read"], conditions: { target_type: "all" } },
350
- { actions: ["read"], conditions: { target_type: "authenticated" } }
351
- ],
352
- SUPPORT: { actions: ["read"] }
344
+ EDITOR: { actions: ["read"] }
353
345
  }
354
346
  }
355
347
  };
@@ -374,6 +366,7 @@ var notificationReadEntity = {
374
366
  casl: {
375
367
  subject: "NotificationRead",
376
368
  permissions: {
369
+ "@": { actions: ["create", "read", "delete"], conditions: { user_id: "${user.id}" } },
377
370
  ADMIN: { actions: ["manage"] }
378
371
  }
379
372
  }
@@ -409,7 +402,7 @@ var sendNotificationAction = {
409
402
  expires_at: useDatetimeField({ label: { en: "Expires At", es: "Expira el" } })
410
403
  },
411
404
  casl: {
412
- subject: "Notification",
405
+ subject: "NotificationItem",
413
406
  permissions: {
414
407
  ADMIN: { actions: ["execute"] }
415
408
  }
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'\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'] as string\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: unknown, callback?: unknown) => {\n const { notificationId } = (data ?? {}) as { notificationId: string }\n const cb = callback as ((res: { success: boolean; error?: string }) => void) | undefined\n try {\n if (!notificationId || typeof notificationId !== 'string') {\n cb?.({ 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(notificationId, userId, roleIds)\n if (!success) {\n cb?.({ success: false, error: 'NOT_FOUND' })\n return\n }\n cb?.({ success: true })\n } catch (err) {\n ctx.core.logger.error({ err, userId, notificationId }, 'Error marking notification as read')\n cb?.({ 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 (...args: unknown[]) => {\n const callback = args[0] as ((res: { success: boolean; count: number; error?: string }) => void) | undefined\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: 'NotificationItem',\n permissions: {\n ADMIN: { actions: ['execute'] }\n }\n }\n } satisfies ActionDefinition\n ],\n casl: {\n subject: 'NotificationItem',\n permissions: {\n '@': [\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 EDITOR: { 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 '@': { actions: ['create', 'read', 'delete'], conditions: { user_id: '${user.id}' } },\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: \"NotificationItem\",\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'] as string\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: unknown, callback?: unknown) => {\n const { notificationId } = (data ?? {}) as { notificationId: string }\n const cb = callback as ((res: { success: boolean; error?: string }) => void) | undefined\n try {\n if (!notificationId || typeof notificationId !== 'string') {\n cb?.({ 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(notificationId, userId, roleIds)\n if (!success) {\n cb?.({ success: false, error: 'NOT_FOUND' })\n return\n }\n cb?.({ success: true })\n } catch (err) {\n ctx.core.logger.error({ err, userId, notificationId }, 'Error marking notification as read')\n cb?.({ 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 (...args: unknown[]) => {\n const callback = args[0] as ((res: { success: boolean; count: number; error?: string }) => void) | undefined\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gzl10/nexus-plugin-notifications",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "description": "Nexus Plugin: Real-time in-app notifications via Socket.IO",
6
6
  "license": "MIT",
@@ -8,10 +8,6 @@
8
8
  ".": {
9
9
  "import": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts"
11
- },
12
- "./client": {
13
- "import": "./dist/client/index.js",
14
- "types": "./dist/client/index.d.ts"
15
11
  }
16
12
  },
17
13
  "files": [
@@ -27,12 +23,14 @@
27
23
  "zod": "^3.24.0"
28
24
  },
29
25
  "devDependencies": {
30
- "@gzl10/nexus-sdk": "0.18.0",
31
- "@gzl10/nexus-client": "0.18.0"
26
+ "@gzl10/nexus-backend": "0.21.0",
27
+ "@gzl10/nexus-sdk": "0.21.0"
32
28
  },
33
29
  "scripts": {
34
30
  "build": "tsup",
35
31
  "lint": "eslint .",
36
- "typecheck": "tsc --noEmit"
32
+ "typecheck": "tsc --noEmit",
33
+ "migrate:dev": "nexus migrate dev",
34
+ "migrate:status": "nexus migrate status"
37
35
  }
38
36
  }
@@ -1,56 +0,0 @@
1
- import { NexusClient, NexusApi } from '@gzl10/nexus-client';
2
-
3
- /**
4
- * @module @gzl10/nexus-plugin-notifications/client
5
- * @description Client API for the Notifications plugin with declaration merging.
6
- */
7
-
8
- interface Notification {
9
- id: string;
10
- type: string;
11
- title: string;
12
- message: string;
13
- read: boolean;
14
- created_at: string;
15
- expires_at?: string;
16
- data?: Record<string, unknown>;
17
- }
18
- interface PaginatedNotifications {
19
- items: Notification[];
20
- total: number;
21
- page: number;
22
- limit: number;
23
- totalPages: number;
24
- hasNext: boolean;
25
- }
26
- interface NotificationsApi {
27
- getUnread(): Promise<PaginatedNotifications>;
28
- getAll(params?: {
29
- page?: number;
30
- limit?: number;
31
- }): Promise<PaginatedNotifications>;
32
- markRead(id: string): Promise<void>;
33
- markAllRead(): Promise<{
34
- count: number;
35
- }>;
36
- }
37
- declare module '@gzl10/nexus-client' {
38
- interface NexusPluginApis {
39
- notifications: NotificationsApi;
40
- }
41
- }
42
- declare function createNotificationsApi(client: NexusClient): NotificationsApi;
43
- /**
44
- * Registers the Notifications API on a NexusApi instance.
45
- *
46
- * @example
47
- * ```typescript
48
- * import { registerNotificationsApi } from '@gzl10/nexus-plugin-notifications/client'
49
- *
50
- * registerNotificationsApi(nexus)
51
- * nexus.plugin('notifications').getUnread()
52
- * ```
53
- */
54
- declare function registerNotificationsApi(nexus: NexusApi): void;
55
-
56
- export { type Notification, type NotificationsApi, type PaginatedNotifications, createNotificationsApi, registerNotificationsApi };
@@ -1,34 +0,0 @@
1
- // src/client/index.ts
2
- function createNotificationsApi(client) {
3
- return {
4
- async getUnread() {
5
- const response = await client.get("/notifications/unread");
6
- return response.data;
7
- },
8
- async getAll(params) {
9
- const query = new URLSearchParams();
10
- if (params?.page) query.set("page", String(params.page));
11
- if (params?.limit) query.set("limit", String(params.limit));
12
- const qs = query.toString();
13
- const response = await client.get(
14
- qs ? `/notifications?${qs}` : "/notifications"
15
- );
16
- return response.data;
17
- },
18
- async markRead(id) {
19
- await client.post(`/notifications/${id}/read`);
20
- },
21
- async markAllRead() {
22
- const response = await client.post("/notifications/read-all");
23
- return response.data;
24
- }
25
- };
26
- }
27
- function registerNotificationsApi(nexus) {
28
- nexus.extend("notifications", (client) => createNotificationsApi(client));
29
- }
30
- export {
31
- createNotificationsApi,
32
- registerNotificationsApi
33
- };
34
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * @module @gzl10/nexus-plugin-notifications/client\n * @description Client API for the Notifications plugin with declaration merging.\n */\n\nimport type { NexusClient, NexusApi } from '@gzl10/nexus-client'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Notification {\n id: string\n type: string\n title: string\n message: string\n read: boolean\n created_at: string\n expires_at?: string\n data?: Record<string, unknown>\n}\n\nexport interface PaginatedNotifications {\n items: Notification[]\n total: number\n page: number\n limit: number\n totalPages: number\n hasNext: boolean\n}\n\nexport interface NotificationsApi {\n getUnread(): Promise<PaginatedNotifications>\n getAll(params?: { page?: number; limit?: number }): Promise<PaginatedNotifications>\n markRead(id: string): Promise<void>\n markAllRead(): Promise<{ count: number }>\n}\n\n// ============================================================================\n// Declaration merging — typed plugin() calls\n// ============================================================================\n\ndeclare module '@gzl10/nexus-client' {\n interface NexusPluginApis {\n notifications: NotificationsApi\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createNotificationsApi(client: NexusClient): NotificationsApi {\n return {\n async getUnread() {\n const response = await client.get<PaginatedNotifications>('/notifications/unread')\n return response.data\n },\n\n async getAll(params) {\n const query = new URLSearchParams()\n if (params?.page) query.set('page', String(params.page))\n if (params?.limit) query.set('limit', String(params.limit))\n const qs = query.toString()\n const response = await client.get<PaginatedNotifications>(\n qs ? `/notifications?${qs}` : '/notifications'\n )\n return response.data\n },\n\n async markRead(id: string) {\n await client.post(`/notifications/${id}/read`)\n },\n\n async markAllRead() {\n const response = await client.post<{ count: number }>('/notifications/read-all')\n return response.data\n }\n }\n}\n\n/**\n * Registers the Notifications API on a NexusApi instance.\n *\n * @example\n * ```typescript\n * import { registerNotificationsApi } from '@gzl10/nexus-plugin-notifications/client'\n *\n * registerNotificationsApi(nexus)\n * nexus.plugin('notifications').getUnread()\n * ```\n */\nexport function registerNotificationsApi(nexus: NexusApi): void {\n nexus.extend('notifications', (client) => createNotificationsApi(client))\n}\n"],"mappings":";AAoDO,SAAS,uBAAuB,QAAuC;AAC5E,SAAO;AAAA,IACL,MAAM,YAAY;AAChB,YAAM,WAAW,MAAM,OAAO,IAA4B,uBAAuB;AACjF,aAAO,SAAS;AAAA,IAClB;AAAA,IAEA,MAAM,OAAO,QAAQ;AACnB,YAAM,QAAQ,IAAI,gBAAgB;AAClC,UAAI,QAAQ,KAAM,OAAM,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACvD,UAAI,QAAQ,MAAO,OAAM,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AAC1D,YAAM,KAAK,MAAM,SAAS;AAC1B,YAAM,WAAW,MAAM,OAAO;AAAA,QAC5B,KAAK,kBAAkB,EAAE,KAAK;AAAA,MAChC;AACA,aAAO,SAAS;AAAA,IAClB;AAAA,IAEA,MAAM,SAAS,IAAY;AACzB,YAAM,OAAO,KAAK,kBAAkB,EAAE,OAAO;AAAA,IAC/C;AAAA,IAEA,MAAM,cAAc;AAClB,YAAM,WAAW,MAAM,OAAO,KAAwB,yBAAyB;AAC/E,aAAO,SAAS;AAAA,IAClB;AAAA,EACF;AACF;AAaO,SAAS,yBAAyB,OAAuB;AAC9D,QAAM,OAAO,iBAAiB,CAAC,WAAW,uBAAuB,MAAM,CAAC;AAC1E;","names":[]}