fix(治理): 完成任务 8 安全与通知闭环修复
- 串行化拉黑、处罚、投瓶和匹配策略检查\n- 完成异步站内通知、未读统计和偏好并发语义\n- 补齐后台查询审计、处罚恢复和隐私测试\n- 稳定 Redis 恢复、匹配锁序及超时测试
This commit is contained in:
@@ -255,8 +255,8 @@ describe("bottles with real PostgreSQL", () => {
|
||||
expiresAt: new Date(Date.now() + 60_000),
|
||||
},
|
||||
});
|
||||
const denied = await create(randomUUID()).expect(403);
|
||||
expect(denied.body.code).toBe("ACCOUNT_SANCTIONED");
|
||||
const denied = await create(randomUUID()).expect(401);
|
||||
expect(denied.body.code).toBe("AUTH_UNAUTHORIZED");
|
||||
expect(await prisma.bottle.count()).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,9 +3,10 @@ import { AuthModule } from "../auth/auth.module.js";
|
||||
import { DatabaseModule } from "../database/database.module.js";
|
||||
import { BottleController } from "./bottle.controller.js";
|
||||
import { BottleService } from "./bottle.service.js";
|
||||
import { SafetyModule } from "../safety/safety.module.js";
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, DatabaseModule],
|
||||
imports: [AuthModule, DatabaseModule, SafetyModule],
|
||||
controllers: [BottleController],
|
||||
providers: [BottleService],
|
||||
})
|
||||
|
||||
@@ -5,20 +5,19 @@ import { Prisma } from "@prisma/client";
|
||||
import { DomainException } from "../common/domain.exception.js";
|
||||
import { PrismaService } from "../database/prisma.service.js";
|
||||
import { utc8UsageDate } from "./usage-date.js";
|
||||
import { SafetyLockService } from "../safety/safety-lock.service.js";
|
||||
|
||||
@Injectable()
|
||||
export class BottleService {
|
||||
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
@Inject(PrismaService) private readonly prisma: PrismaService,
|
||||
@Inject(SafetyLockService) private readonly locks: SafetyLockService,
|
||||
) {}
|
||||
|
||||
async create(authorId: string, requestId: string, contentText: string) {
|
||||
const existing = await this.prisma.bottle.findUnique({
|
||||
where: {
|
||||
authorId_clientRequestId: { authorId, clientRequestId: requestId },
|
||||
},
|
||||
});
|
||||
if (existing) return this.resolveIdempotent(existing, contentText);
|
||||
try {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
await this.locks.lockAccounts(tx, [authorId]);
|
||||
await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${`${authorId}:${requestId}`}, 0))`;
|
||||
const duplicate = await tx.bottle.findUnique({
|
||||
where: {
|
||||
@@ -27,6 +26,10 @@ export class BottleService {
|
||||
});
|
||||
if (duplicate) return this.resolveIdempotent(duplicate, contentText);
|
||||
const now = new Date();
|
||||
const account = await tx.account.findUnique({
|
||||
where: { id: authorId },
|
||||
select: { status: true },
|
||||
});
|
||||
const sanctioned = await tx.sanction.findFirst({
|
||||
where: {
|
||||
accountId: authorId,
|
||||
@@ -37,7 +40,7 @@ export class BottleService {
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (sanctioned)
|
||||
if (account?.status !== "ACTIVE" || sanctioned)
|
||||
throw new DomainException(
|
||||
ErrorCode.ACCOUNT_SANCTIONED,
|
||||
"Account sanctioned",
|
||||
|
||||
Reference in New Issue
Block a user