import { Controller, Get, Post, Body, Query, Headers, UnauthorizedException, ParseIntPipe, DefaultValuePipe, } from '@nestjs/common'; import { NotificationRepository } from '../../../infrastructure/repositories/notification.repository'; import * as jwt from 'jsonwebtoken'; /** * User-facing notification endpoints. * Kong enforces JWT — we extract tenantId/userId from Authorization header. */ @Controller('api/v1/notifications') export class NotificationUserController { constructor(private readonly repo: NotificationRepository) {} /** GET /api/v1/notifications/me — notifications visible to this user */ @Get('me') async getMyNotifications( @Headers('authorization') auth: string, @Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit: number, @Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number, ) { const { tenantId, userId } = this.extractJwt(auth); return this.repo.findForUser({ tenantId, userId, limit: Math.min(limit, 100), offset }); } /** GET /api/v1/notifications/me/unread-count */ @Get('me/unread-count') async getUnreadCount(@Headers('authorization') auth: string) { const { tenantId, userId } = this.extractJwt(auth); const count = await this.repo.getUnreadCount(tenantId, userId); return { count }; } /** * POST /api/v1/notifications/me/mark-read * Body: { notificationId?: string } — omit to mark ALL as read */ @Post('me/mark-read') async markRead( @Headers('authorization') auth: string, @Body() body: { notificationId?: string }, ) { const { tenantId, userId } = this.extractJwt(auth); if (body?.notificationId) { await this.repo.markRead(body.notificationId, userId, tenantId); } else { await this.repo.markAllRead(tenantId, userId); } return { ok: true }; } private extractJwt(auth: string): { tenantId: string; userId: string } { if (!auth?.startsWith('Bearer ')) { throw new UnauthorizedException('Missing authorization header'); } const token = auth.slice(7); const secret = process.env.JWT_SECRET || 'dev-secret'; try { const payload = jwt.verify(token, secret) as any; return { tenantId: payload.tenantId, userId: payload.sub }; } catch { throw new UnauthorizedException('Invalid JWT'); } } }