91 lines
2.5 KiB
TypeScript
91 lines
2.5 KiB
TypeScript
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<MessageEvent> {
|
||
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 };
|
||
}
|
||
}
|