feat: 完成P13治理收尾并创建P14执行合同(Issue: P13-1,P13-2,P13-3,P13-4,P13-5,P13-6,P13-7,P13-8,P14-0)
这个提交包含在:
@@ -3,6 +3,10 @@ const { z } = require("zod");
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const { sendData } = require("../utils/reply");
|
||||
const {
|
||||
buildDataEnvelopeSchema,
|
||||
buildErrorEnvelopeSchema,
|
||||
} = require("../utils/openapi");
|
||||
const {
|
||||
FEATURE_FLAG_KEY_LIST,
|
||||
OVERRIDE_MODE_LIST,
|
||||
@@ -24,6 +28,49 @@ const UPDATE_BODY_SCHEMA = z.object({
|
||||
expectedVersion: z.coerce.number().int().min(1),
|
||||
});
|
||||
|
||||
const FEATURE_FLAG_ITEM_SCHEMA = {
|
||||
type: "object",
|
||||
required: [
|
||||
"id",
|
||||
"flagKey",
|
||||
"overrideMode",
|
||||
"rolloutType",
|
||||
"departmentIds",
|
||||
"note",
|
||||
"version",
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"updatedAt",
|
||||
"updatedBy",
|
||||
"envEnabled",
|
||||
"effectiveEnabled",
|
||||
"effectiveReason",
|
||||
"matchedDepartmentId",
|
||||
"requiresDepartment",
|
||||
],
|
||||
properties: {
|
||||
id: { anyOf: [{ type: "string", format: "uuid" }, { type: "null" }] },
|
||||
flagKey: { type: "string", enum: FEATURE_FLAG_KEY_LIST },
|
||||
overrideMode: { type: "string", enum: OVERRIDE_MODE_LIST },
|
||||
rolloutType: { type: "string", enum: ROLLOUT_TYPE_LIST },
|
||||
departmentIds: {
|
||||
type: "array",
|
||||
items: { type: "string", format: "uuid" },
|
||||
},
|
||||
note: { type: "string" },
|
||||
version: { type: "integer" },
|
||||
createdAt: { anyOf: [{ type: "string", format: "date-time" }, { type: "null" }] },
|
||||
createdBy: { anyOf: [{ type: "string", format: "uuid" }, { type: "null" }] },
|
||||
updatedAt: { anyOf: [{ type: "string", format: "date-time" }, { type: "null" }] },
|
||||
updatedBy: { anyOf: [{ type: "string", format: "uuid" }, { type: "null" }] },
|
||||
envEnabled: { type: "boolean" },
|
||||
effectiveEnabled: { type: "boolean" },
|
||||
effectiveReason: { type: "string" },
|
||||
matchedDepartmentId: { anyOf: [{ type: "string", format: "uuid" }, { type: "null" }] },
|
||||
requiresDepartment: { type: "boolean" },
|
||||
},
|
||||
};
|
||||
|
||||
function parseKey(params) {
|
||||
const parsed = KEY_PARAM_SCHEMA.safeParse(params ?? {});
|
||||
if (!parsed.success) {
|
||||
@@ -66,6 +113,31 @@ async function featureFlagsRoutes(fastify) {
|
||||
"/system/feature-flags",
|
||||
{
|
||||
preHandler: fastify.requirePermission("system.settings"),
|
||||
schema: {
|
||||
tags: ["system", "feature-flags"],
|
||||
summary: "获取功能开关列表",
|
||||
security: [{ bearerAuth: [] }],
|
||||
response: {
|
||||
200: buildDataEnvelopeSchema(
|
||||
{
|
||||
type: "object",
|
||||
required: ["items"],
|
||||
properties: {
|
||||
items: {
|
||||
type: "array",
|
||||
items: FEATURE_FLAG_ITEM_SCHEMA,
|
||||
},
|
||||
},
|
||||
},
|
||||
"功能开关列表",
|
||||
),
|
||||
403: buildErrorEnvelopeSchema({
|
||||
description: "无权限",
|
||||
codeExample: "PERM_001",
|
||||
messageExample: "无权限",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const items = await listFeatureFlags(fastify.prisma, fastify.config);
|
||||
@@ -77,6 +149,64 @@ async function featureFlagsRoutes(fastify) {
|
||||
"/system/feature-flags/:key",
|
||||
{
|
||||
preHandler: fastify.requirePermission("system.settings"),
|
||||
schema: {
|
||||
tags: ["system", "feature-flags"],
|
||||
summary: "更新功能开关",
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["key"],
|
||||
properties: {
|
||||
key: { type: "string", enum: FEATURE_FLAG_KEY_LIST },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["overrideMode", "rolloutType", "departmentIds", "expectedVersion"],
|
||||
properties: {
|
||||
overrideMode: { type: "string", enum: OVERRIDE_MODE_LIST },
|
||||
rolloutType: { type: "string", enum: ROLLOUT_TYPE_LIST },
|
||||
departmentIds: {
|
||||
type: "array",
|
||||
items: { type: "string", format: "uuid" },
|
||||
},
|
||||
note: { type: "string", maxLength: 500 },
|
||||
expectedVersion: { type: "integer", minimum: 1 },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: buildDataEnvelopeSchema(
|
||||
{
|
||||
type: "object",
|
||||
required: ["item"],
|
||||
properties: {
|
||||
item: FEATURE_FLAG_ITEM_SCHEMA,
|
||||
},
|
||||
},
|
||||
"更新后的功能开关",
|
||||
),
|
||||
400: buildErrorEnvelopeSchema({
|
||||
description: "参数校验失败",
|
||||
codeExample: "VALID_001",
|
||||
messageExample: "参数校验失败",
|
||||
}),
|
||||
404: buildErrorEnvelopeSchema({
|
||||
description: "开关不存在",
|
||||
codeExample: "SYS_404",
|
||||
messageExample: "Not Found",
|
||||
}),
|
||||
409: buildErrorEnvelopeSchema({
|
||||
description: "版本冲突",
|
||||
codeExample: "VALID_001",
|
||||
messageExample: "参数校验失败",
|
||||
detailsExample: {
|
||||
featureFlag: "version_conflict",
|
||||
expectedVersion: 2,
|
||||
currentVersion: 3,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const key = parseKey(request.params);
|
||||
|
||||
在新工单中引用
屏蔽一个用户