Feat: Implementar sistema de notificaciones con historial por usuario
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 48s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 48s
- Crear composable useNotifications con gestión por localStorage - Almacenamiento separado por usuario usando UID de Authentik - Auto-limpieza de notificaciones mayores a 30 días - Sincronización automática entre pestañas - Filtrado por tipo, búsqueda y gestión completa - Crear wrapper useToast para guardar toasts automáticamente - Intercepta todos los toasts de la aplicación - Guarda historial sin afectar funcionalidad existente - Implementar endpoints de API para notificaciones del backend - POST /api/notifications/send para enviar notificaciones - GET /api/notifications/list para obtener pendientes - Actualizar página de notificaciones con funcionalidad real - Búsqueda y filtros por tipo (info, warning, success, error) - Eliminar individual o todas las notificaciones - Marcar como leídas individual o todas - Badges de origen (toast, backend, manual) - Estados vacíos con mensajes informativos - Actualizar badge del sidebar con contador dinámico - Muestra número real de notificaciones no leídas - Se oculta cuando no hay notificaciones - Inicializar sistema en app.vue - Auto-inicialización al montar la app - Limpieza automática de notificaciones antiguas
This commit is contained in:
95
nuxt4-app/server/api/notifications/send.post.ts
Normal file
95
nuxt4-app/server/api/notifications/send.post.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Endpoint para enviar notificaciones a usuarios específicos desde el backend
|
||||
*
|
||||
* Esta API permite crear notificaciones que se entregarán directamente
|
||||
* al cliente para ser guardadas en su localStorage.
|
||||
*
|
||||
* Casos de uso:
|
||||
* - Notificaciones del sistema
|
||||
* - Alertas generadas por procesos del backend
|
||||
* - Notificaciones administrativas
|
||||
*/
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Leer el body de la petición
|
||||
const body = await readBody(event)
|
||||
|
||||
// Validar campos requeridos
|
||||
if (!body.targetUserId || !body.title) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Faltan campos requeridos: targetUserId y title'
|
||||
})
|
||||
}
|
||||
|
||||
// Validar tipo de notificación
|
||||
const validTypes = ['info', 'warning', 'success', 'error']
|
||||
const type = body.type || 'info'
|
||||
if (!validTypes.includes(type)) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `Tipo inválido. Debe ser uno de: ${validTypes.join(', ')}`
|
||||
})
|
||||
}
|
||||
|
||||
// Leer el usuario actual de los headers de Authentik
|
||||
const headers = getHeaders(event)
|
||||
const currentUserId = headers['x-authentik-uid']
|
||||
const currentUsername = headers['x-authentik-username']
|
||||
|
||||
// Verificar que el usuario esté autenticado
|
||||
if (!currentUserId) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Usuario no autenticado'
|
||||
})
|
||||
}
|
||||
|
||||
// Opcional: verificar permisos (por ejemplo, solo admins pueden enviar notificaciones)
|
||||
// const groups = headers['x-authentik-groups']?.split('|') || []
|
||||
// if (!groups.includes('admins')) {
|
||||
// throw createError({
|
||||
// statusCode: 403,
|
||||
// statusMessage: 'No tienes permisos para enviar notificaciones'
|
||||
// })
|
||||
// }
|
||||
|
||||
// Construir la notificación
|
||||
const notification = {
|
||||
targetUserId: body.targetUserId,
|
||||
type: type as 'info' | 'warning' | 'success' | 'error',
|
||||
title: body.title,
|
||||
message: body.message || '',
|
||||
origin: 'backend' as const,
|
||||
metadata: {
|
||||
sentBy: currentUsername,
|
||||
sentAt: new Date().toISOString(),
|
||||
...body.metadata
|
||||
}
|
||||
}
|
||||
|
||||
// Nota: Como usamos localStorage en el cliente, no guardamos en BD
|
||||
// En su lugar, retornamos la notificación para que el cliente la guarde
|
||||
// El cliente debe hacer polling o usar SSE/WebSocket para obtener notificaciones
|
||||
|
||||
return {
|
||||
success: true,
|
||||
notification,
|
||||
message: 'Notificación creada correctamente'
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error al enviar notificación:', error)
|
||||
|
||||
// Si ya es un error de H3, relanzarlo
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// Error genérico
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Error al procesar la notificación'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user