feat: add api http conventions and booking state rules
This commit is contained in:
@@ -132,6 +132,15 @@ export class BookingService {
|
||||
) {
|
||||
const booking = await this.prisma.booking.findFirst({ where: { id, brandId } });
|
||||
if (!booking) throw new NotFoundException("订单不存在");
|
||||
const allowed: Record<string, string[]> = {
|
||||
received: ["contacting", "cancelled"],
|
||||
contacting: ["confirmed", "cancelled"],
|
||||
confirmed: ["completed", "cancelled"],
|
||||
completed: [],
|
||||
cancelled: [],
|
||||
};
|
||||
if (status !== booking.status && !allowed[booking.status]?.includes(status))
|
||||
throw new BadRequestException("不允许的订单状态流转");
|
||||
return this.prisma.booking.update({
|
||||
where: { id },
|
||||
data: { status, ...(adminNote !== undefined ? { adminNote } : {}), isRead: true },
|
||||
|
||||
32
apps/api/src/http.ts
Normal file
32
apps/api/src/http.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Catch, HttpException, Injectable } from "@nestjs/common";
|
||||
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
@Injectable()
|
||||
export class RequestIdMiddleware {
|
||||
use(request: FastifyRequest, reply: FastifyReply, next: () => void): void {
|
||||
const requestId = request.headers["x-request-id"]?.toString() ?? randomUUID();
|
||||
reply.header("x-request-id", requestId);
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
@Catch()
|
||||
export class ApiExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost): void {
|
||||
const context = host.switchToHttp();
|
||||
const response = context.getResponse<FastifyReply>();
|
||||
const request = context.getRequest<FastifyRequest>();
|
||||
const status = exception instanceof HttpException ? exception.getStatus() : 500;
|
||||
const detail = exception instanceof HttpException ? exception.getResponse() : "服务器内部错误";
|
||||
response.status(status).send({
|
||||
data: null,
|
||||
error: {
|
||||
code: `HTTP_${status}`,
|
||||
message: typeof detail === "string" ? detail : "请求失败",
|
||||
},
|
||||
requestId: request.headers["x-request-id"] ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { AdminAuthService } from "./admin-auth.service.js";
|
||||
import { AdminLoginDto } from "./admin-auth.dto.js";
|
||||
import type { FastifyRequest } from "fastify";
|
||||
import { readIdentity, signIdentity } from "./auth.js";
|
||||
import { ApiExceptionFilter, RequestIdMiddleware } from "./http.js";
|
||||
|
||||
@Controller("health")
|
||||
export class HealthController {
|
||||
@@ -250,6 +251,8 @@ export async function createApp(): Promise<NestFastifyApplication> {
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }),
|
||||
);
|
||||
app.useGlobalFilters(new ApiExceptionFilter());
|
||||
app.use(new RequestIdMiddleware().use.bind(new RequestIdMiddleware()));
|
||||
app.enableCors();
|
||||
return app;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user