Skip to content

Notifications

Planner uses real-time notifications delivered via WebSocket (Socket.IO). The NotificationsModule is @Global(), so any service can inject NotificationsService directly.

Architecture

  • Namespace: /notifications
  • Auth: Token-based via handshake.auth.token using JwtService
  • Rooms: Users join user:<id> room on connection
  • Delivery: Emitted as notification:new event to the user's room

Notification Types

TypeTriggerData
task.assignedTask assigned to user{ taskId, projectId }
task.status_changedTask status updated{ taskId, projectId, oldStatus, newStatus }
task.comment_addedNew comment or reply{ taskId, projectId, commentId }
team.member.addedUser added to team{ teamId, teamSlug }
org.member.addedUser added to org{ orgId }
project.member.addedUser added to project{ projectId }

API Endpoints

MethodPathDescription
GET/api/notificationsList user's notifications
GET/api/notifications/unread-countUnread count
PATCH/api/notifications/:id/readMark as read
PATCH/api/notifications/read-allMark all as read
DELETE/api/notifications/:idDelete notification

Prisma Model

prisma
model Notification {
  id        String   @id @default(uuid())
  userId    String
  type      String
  title     String
  body      String?
  data      Json?    // Arbitrary event data for deep-linking
  readAt    DateTime?
  createdAt DateTime @default(now())

  user User @relation(fields: [userId], references: [id])

  @@index([userId, readAt])
  @@index([userId, createdAt])
}

Frontend

Notification Store

typescript
const store = useNotificationsStore()
const { notifications, unreadCount, loading } = storeToRefs(store)

await store.fetchNotifications()
await store.fetchUnreadCount()
await store.markAsRead(id)
await store.markAllAsRead()
await store.deleteNotification(id)

Socket Listener

The frontend connects to the /notifications namespace on auth and listens for notification:new events:

typescript
socket.on('notification:new', (notification) => {
  notifications.value.unshift(notification)
  unreadCount.value++
})

Key Implementation Details

  • NotificationsService.create() is fire-and-forget (not awaited) — it doesn't slow down the main operation
  • The data JSON field stores structured payload (taskId, projectId, etc.) for deep-linking from the notification center
  • Comment replies notify the parent comment author (not just task creator/assignee)
  • Unread count is displayed as a badge in the header