import { Controller, Get, Put, Param, ParseIntPipe, Query, Req, Sse, UseGuards, } from '@nestjs/common'; import { Request } from 'express'; import { Observable, interval, map, merge } from 'rxjs'; import { NotificationsService } from './notifications.service'; import { NotificationQueryDto } from './dto/notification.dto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { RequirePermission } from '../auth/decorators/permission.decorator'; interface AuthenticatedUser { id: number; username: string; permissions: string[]; } interface AuthenticatedRequest extends Request { user: AuthenticatedUser; } @UseGuards(JwtAuthGuard) @RequirePermission('notification:view') @Controller('notifications') export class NotificationsController { constructor(private readonly service: NotificationsService) {} @Get() async findAll( @Req() req: AuthenticatedRequest, @Query() query: NotificationQueryDto, ) { const userId = req.user.id; return this.service.findByUser(userId, query.after, query.limit ?? 20); } @Get('unread-count') async unreadCount(@Req() req: AuthenticatedRequest) { const userId = req.user.id; const count = await this.service.getUnreadCount(userId); return { count }; } @Sse('stream') stream(@Req() req: AuthenticatedRequest): Observable { const userId = req.user.id; req.on('close', () => { this.service.unsubscribe(userId); }); // 每 25s 发送一次空消息作为心跳,避免空闲连接被 Nginx 等中间层超时掐断。 // 空 data 会被前端 EventSource 收到并忽略(JSON.parse 失败)。 return merge( this.service.subscribe(userId).pipe( map((notification) => ({ data: JSON.stringify({ id: notification.id, type: notification.type, title: notification.title, content: notification.content, link: notification.link, createdAt: notification.createdAt, }), } as MessageEvent)), ), interval(25_000).pipe(map(() => ({ data: '' } as MessageEvent))), ); } @Put(':id/read') async markRead( @Param('id', ParseIntPipe) id: number, @Req() req: AuthenticatedRequest, ) { await this.service.markRead(+id, req.user.id); return { success: true }; } @Put('read-all') async markAllRead(@Req() req: AuthenticatedRequest) { await this.service.markAllRead(req.user.id); return { success: true }; } }