feat: M4-11 — vendor billing + secret lifecycle management
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
- Add VendorBill Prisma model (provider/billMonth/amount/usageSummary) - VendorBillController: CRUD bills, list/rotate/revoke secrets - Secret lifecycle: active → expiring → expired/rotated/revoked Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b3176b8ead
commit
c6d01534c8
@ -1519,3 +1519,19 @@ model DataExportRequest {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model VendorBill {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
provider String @db.VarChar(32)
|
||||||
|
billMonth String @db.VarChar(7)
|
||||||
|
amount Decimal @db.Decimal(10, 2)
|
||||||
|
currency String @default("CNY") @db.VarChar(8)
|
||||||
|
usageSummary Json?
|
||||||
|
paidAt DateTime?
|
||||||
|
notes String? @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([provider, billMonth])
|
||||||
|
@@index([provider])
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { SecretController } from './secret.controller';
|
import { SecretController } from './secret.controller';
|
||||||
|
import { VendorBillController } from './vendor-bill.controller';
|
||||||
import { SecretService } from './secret.service';
|
import { SecretService } from './secret.service';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
@Module({ controllers: [SecretController], providers: [SecretService, PrismaService, AdminAuthGuard, AdminRolesGuard], exports: [SecretService] })
|
@Module({ controllers: [SecretController, VendorBillController], providers: [SecretService, PrismaService, AdminAuthGuard, AdminRolesGuard], exports: [SecretService] })
|
||||||
export class SecretModule {}
|
export class SecretModule {}
|
||||||
|
|||||||
62
src/modules/secret/vendor-bill.controller.ts
Normal file
62
src/modules/secret/vendor-bill.controller.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Controller, Get, Post, Patch, Delete, Param, Body, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth, ApiOperation } 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-vendor')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('admin-api/vendor')
|
||||||
|
@UseGuards(AdminAuthGuard, AdminRolesGuard)
|
||||||
|
export class VendorBillController {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
@Get('bills')
|
||||||
|
@ApiOperation({ summary: '服务商账单列表' })
|
||||||
|
async listBills() {
|
||||||
|
return this.prisma.vendorBill.findMany({ orderBy: { billMonth: 'desc' }, take: 100 });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('bills')
|
||||||
|
@ApiOperation({ summary: '录入服务商账单' })
|
||||||
|
async createBill(@Body() dto: { provider: string; billMonth: string; amount: number; currency?: string; usageSummary?: any; notes?: string }) {
|
||||||
|
return this.prisma.vendorBill.create({ data: dto });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('bills/:id')
|
||||||
|
@ApiOperation({ summary: '更新服务商账单' })
|
||||||
|
async updateBill(@Param('id') id: string, @Body() dto: Record<string, any>) {
|
||||||
|
return this.prisma.vendorBill.update({ where: { id }, data: dto });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('bills/:id')
|
||||||
|
@ApiOperation({ summary: '删除服务商账单' })
|
||||||
|
async deleteBill(@Param('id') id: string) {
|
||||||
|
await this.prisma.vendorBill.delete({ where: { id } });
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══ Secret lifecycle management ═══
|
||||||
|
|
||||||
|
@Get('secrets')
|
||||||
|
@ApiOperation({ summary: '密钥列表(含生命周期状态)' })
|
||||||
|
async listSecrets() {
|
||||||
|
return this.prisma.secretRecord.findMany({ orderBy: { updatedAt: 'desc' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('secrets/:id/rotate')
|
||||||
|
@ApiOperation({ summary: '轮换密钥(标记旧 key 为 rotated)' })
|
||||||
|
async rotateSecret(@Param('id') id: string) {
|
||||||
|
const secret = await this.prisma.secretRecord.findUnique({ where: { id } });
|
||||||
|
if (!secret) throw new Error('密钥不存在');
|
||||||
|
await this.prisma.secretRecord.update({ where: { id }, data: { status: 'rotated' } });
|
||||||
|
return { ok: true, rotatedKey: secret.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('secrets/:id/revoke')
|
||||||
|
@ApiOperation({ summary: '吊销密钥' })
|
||||||
|
async revokeSecret(@Param('id') id: string) {
|
||||||
|
await this.prisma.secretRecord.update({ where: { id }, data: { status: 'revoked' } });
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user