feat: M4-10 — admin notification deepening (cost alerts, import failures, key expirations)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 41s

- Add scope field to Notification model (user/admin)
- AdminNotificationsController: list, send, mark read
- Generate endpoints: cost-alert, import-failure, key-expiring

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
WangDL 2026-05-24 18:23:35 +08:00
parent c4089129c0
commit b3176b8ead
4 changed files with 98 additions and 0 deletions

View File

@ -586,6 +586,7 @@ model Notification {
title String @db.VarChar(255) title String @db.VarChar(255)
content String? @db.Text content String? @db.Text
data Json? data Json?
scope String @default("user") @db.VarChar(16)
readAt DateTime? readAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())

View File

@ -58,6 +58,7 @@ import { ProjectCenterModule } from './modules/project-center/project-center.mod
import { HermesAgentModule } from './modules/hermes-agent/hermes-agent.module'; import { HermesAgentModule } from './modules/hermes-agent/hermes-agent.module';
import { ReleaseModule } from './modules/release/release.module'; import { ReleaseModule } from './modules/release/release.module';
import { ComplianceModule } from './modules/compliance/compliance.module'; import { ComplianceModule } from './modules/compliance/compliance.module';
import { AdminNotificationsModule } from './modules/admin-notifications/admin-notifications.module';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard'; import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
import { RolesGuard } from './common/guards/roles.guard'; import { RolesGuard } from './common/guards/roles.guard';
@ -161,6 +162,7 @@ import appleConfig from './config/apple.config';
HermesAgentModule, HermesAgentModule,
ReleaseModule, ReleaseModule,
ComplianceModule, ComplianceModule,
AdminNotificationsModule,
], ],
providers: [ providers: [
{ provide: APP_GUARD, useClass: RateLimitGuard }, { provide: APP_GUARD, useClass: RateLimitGuard },

View File

@ -0,0 +1,88 @@
import { Controller, Get, Post, Patch, Param, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody } from '@nestjs/swagger';
import { PrismaService } from '../../infrastructure/database/prisma.service';
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
@ApiTags('admin-notifications-v2')
@ApiBearerAuth()
@Controller('admin-api/admin-notifications')
@UseGuards(AdminAuthGuard, AdminRolesGuard)
export class AdminNotificationsController {
constructor(private readonly prisma: PrismaService) {}
@Get()
@ApiOperation({ summary: 'Admin 通知列表' })
async list() {
return this.prisma.notification.findMany({
where: { scope: 'admin' },
orderBy: { createdAt: 'desc' },
take: 100,
});
}
@Post('send')
@ApiOperation({ summary: '发送 Admin 通知' })
@ApiBody({ schema: { type: 'object', required: ['title', 'type'], properties: {
title: { type: 'string' }, type: { type: 'string' }, content: { type: 'string' }, data: { type: 'object' },
} } })
async send(@Body() dto: { title: string; type: string; content?: string; data?: any }) {
return this.prisma.notification.create({
data: {
userId: 'admin',
type: dto.type,
title: dto.title,
content: dto.content || '',
data: dto.data || null,
scope: 'admin',
},
});
}
@Post(':id/read')
@ApiOperation({ summary: '标记 Admin 通知已读' })
async markRead(@Param('id') id: string) {
return this.prisma.notification.update({ where: { id }, data: { readAt: new Date() } });
}
// ═══ Predefined admin notification generators ═══
@Post('generate/cost-alert')
@ApiOperation({ summary: '生成成本预警通知' })
async generateCostAlert(@Body() dto: { cost: number; threshold: number; period: string }) {
return this.prisma.notification.create({
data: {
userId: 'admin', type: 'cost_alert', scope: 'admin',
title: `成本预警:${dto.period}成本 ¥${dto.cost} 超过阈值 ¥${dto.threshold}`,
content: `${dto.period} AI 调用成本已达到 ¥${dto.cost},超过预设阈值 ¥${dto.threshold}`,
data: dto,
},
});
}
@Post('generate/import-failure')
@ApiOperation({ summary: '生成导入失败通知' })
async generateImportFailure(@Body() dto: { importId: string; failedCount: number; totalCount: number }) {
return this.prisma.notification.create({
data: {
userId: 'admin', type: 'import_failure', scope: 'admin',
title: `导入任务大量失败:${dto.failedCount}/${dto.totalCount}`,
content: `导入任务 ${dto.importId}${dto.failedCount}/${dto.totalCount} 项失败。`,
data: dto,
},
});
}
@Post('generate/key-expiring')
@ApiOperation({ summary: '生成 API Key 到期通知' })
async generateKeyExpiring(@Body() dto: { keyName: string; expiresInDays: number }) {
return this.prisma.notification.create({
data: {
userId: 'admin', type: 'key_expiring', scope: 'admin',
title: `API Key「${dto.keyName}」即将在 ${dto.expiresInDays} 天后到期`,
content: `请尽快续期或更换密钥。`,
data: dto,
},
});
}
}

View File

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AdminNotificationsController } from './admin-notifications.controller';
@Module({
controllers: [AdminNotificationsController],
})
export class AdminNotificationsModule {}