feat: 新增项目标签重置接口与n8n流程
This commit is contained in:
@@ -260,6 +260,88 @@ export const TagMaintenanceRequestSchema = z.object({
|
||||
});
|
||||
});
|
||||
|
||||
// ================================
|
||||
// Project Tag Reset API Schemas
|
||||
// ================================
|
||||
|
||||
const ResettableTagCategorySchema = z.enum([
|
||||
"FIXED_PROJECT_TYPE",
|
||||
"TECH_STACK",
|
||||
"AI_PARADIGM",
|
||||
"PRODUCT_FORM",
|
||||
"DOMAIN_SCENARIO",
|
||||
]);
|
||||
|
||||
const ProjectTagSelectionByCategorySchema = z.object({
|
||||
FIXED_PROJECT_TYPE: z.array(z.string().trim().min(1)).min(1).max(1),
|
||||
TECH_STACK: z.array(z.string().trim().min(1)).max(20).default([]),
|
||||
AI_PARADIGM: z.array(z.string().trim().min(1)).max(20).default([]),
|
||||
PRODUCT_FORM: z.array(z.string().trim().min(1)).max(20).default([]),
|
||||
DOMAIN_SCENARIO: z.array(z.string().trim().min(1)).max(20).default([]),
|
||||
});
|
||||
|
||||
export const ProjectTagResetItemSchema = z.object({
|
||||
projectSlug: z.string().trim().min(1).max(200),
|
||||
selectedTagSlugsByCategory: ProjectTagSelectionByCategorySchema,
|
||||
});
|
||||
|
||||
export const ProjectTagResetRequestSchema = z.object({
|
||||
apiKey: z.string().min(32, "Invalid API key format"),
|
||||
dryRun: z.boolean().default(false),
|
||||
replaceAllCategories: z.boolean().default(true),
|
||||
categories: z.array(ResettableTagCategorySchema).min(1).default([
|
||||
"FIXED_PROJECT_TYPE",
|
||||
"TECH_STACK",
|
||||
"AI_PARADIGM",
|
||||
"PRODUCT_FORM",
|
||||
"DOMAIN_SCENARIO",
|
||||
]),
|
||||
projects: z.array(ProjectTagResetItemSchema).min(1).max(100),
|
||||
}).superRefine((data, ctx) => {
|
||||
const seenProjectSlugs = new Set<string>();
|
||||
|
||||
data.projects.forEach((project, projectIndex) => {
|
||||
const normalizedSlug = project.projectSlug.trim().toLowerCase();
|
||||
if (seenProjectSlugs.has(normalizedSlug)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["projects", projectIndex, "projectSlug"],
|
||||
message: `Duplicate projectSlug: ${project.projectSlug}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
seenProjectSlugs.add(normalizedSlug);
|
||||
|
||||
const categories = Object.keys(project.selectedTagSlugsByCategory) as Array<
|
||||
keyof z.infer<typeof ProjectTagSelectionByCategorySchema>
|
||||
>;
|
||||
|
||||
categories.forEach((category) => {
|
||||
const selectedSlugs = project.selectedTagSlugsByCategory[category];
|
||||
const seenTagSlugs = new Set<string>();
|
||||
|
||||
selectedSlugs.forEach((slug, slugIndex) => {
|
||||
const normalizedTagSlug = slug.trim().toLowerCase();
|
||||
if (seenTagSlugs.has(normalizedTagSlug)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [
|
||||
"projects",
|
||||
projectIndex,
|
||||
"selectedTagSlugsByCategory",
|
||||
category,
|
||||
slugIndex,
|
||||
],
|
||||
message: `Duplicate tag slug "${slug}" in category ${category}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
seenTagSlugs.add(normalizedTagSlug);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ================================
|
||||
// Types
|
||||
// ================================
|
||||
@@ -268,3 +350,7 @@ export type TagUpdate = z.infer<typeof TagUpdateSchema>;
|
||||
export type MergeTarget = z.infer<typeof MergeTargetSchema>;
|
||||
export type TagMerge = z.infer<typeof TagMergeSchema>;
|
||||
export type TagMaintenanceRequest = z.infer<typeof TagMaintenanceRequestSchema>;
|
||||
export type ResettableTagCategory = z.infer<typeof ResettableTagCategorySchema>;
|
||||
export type ProjectTagSelectionByCategory = z.infer<typeof ProjectTagSelectionByCategorySchema>;
|
||||
export type ProjectTagResetItem = z.infer<typeof ProjectTagResetItemSchema>;
|
||||
export type ProjectTagResetRequest = z.infer<typeof ProjectTagResetRequestSchema>;
|
||||
|
||||
Reference in New Issue
Block a user