forked from EduCraft/curriculum-project-hub
feat: validate team slug format and improve error handling in team creation
This commit is contained in:
@@ -38,12 +38,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SLUG_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
||||||
|
|
||||||
async function createTeam() {
|
async function createTeam() {
|
||||||
if (!newSlug.trim() || !newName.trim()) return;
|
if (!newSlug.trim() || !newName.trim()) return;
|
||||||
|
const teamSlug = newSlug.trim().toLowerCase();
|
||||||
|
if (!SLUG_RE.test(teamSlug)) {
|
||||||
|
toastError('标识须为小写字母数字,可用连字符连接');
|
||||||
|
return;
|
||||||
|
}
|
||||||
adding = true;
|
adding = true;
|
||||||
try {
|
try {
|
||||||
await api.createTeam(slug, {
|
await api.createTeam(slug, {
|
||||||
slug: newSlug.trim(),
|
slug: teamSlug,
|
||||||
name: newName.trim(),
|
name: newName.trim(),
|
||||||
...(newDesc.trim() ? { description: newDesc.trim() } : {})
|
...(newDesc.trim() ? { description: newDesc.trim() } : {})
|
||||||
});
|
});
|
||||||
@@ -134,7 +141,7 @@
|
|||||||
<div class="saas-card-pad mb-6">
|
<div class="saas-card-pad mb-6">
|
||||||
<h2 class="saas-section-title mb-4">新建团队</h2>
|
<h2 class="saas-section-title mb-4">新建团队</h2>
|
||||||
<div class="grid gap-3 md:grid-cols-[1fr_1fr_1.2fr_auto]">
|
<div class="grid gap-3 md:grid-cols-[1fr_1fr_1.2fr_auto]">
|
||||||
<input class="saas-input" placeholder="标识(小写字母数字)" bind:value={newSlug} />
|
<input class="saas-input" placeholder="标识(如 math-g7)" bind:value={newSlug} />
|
||||||
<input class="saas-input" placeholder="名称" bind:value={newName} />
|
<input class="saas-input" placeholder="名称" bind:value={newName} />
|
||||||
<input class="saas-input" placeholder="描述(可选)" bind:value={newDesc} />
|
<input class="saas-input" placeholder="描述(可选)" bind:value={newDesc} />
|
||||||
<button class="saas-btn-primary" onclick={createTeam} disabled={adding}>新建</button>
|
<button class="saas-btn-primary" onclick={createTeam} disabled={adding}>新建</button>
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ export async function handleRouteError(reply: FastifyReply, err: unknown): Promi
|
|||||||
await sendError(reply, mapped.statusCode, mapped.code, mapped.message);
|
await sendError(reply, mapped.statusCode, mapped.code, mapped.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
reply.log.error({ err }, "unmapped route error");
|
||||||
await sendError(reply, 500, "internal_error", "internal error");
|
await sendError(reply, 500, "internal_error", "internal error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
reply.log.error({ err }, "unmapped route error");
|
||||||
await sendError(reply, 500, "internal_error", "internal error");
|
await sendError(reply, 500, "internal_error", "internal error");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +73,8 @@ function mapDomainError(message: string): { statusCode: number; code: string; me
|
|||||||
lower.includes("is required") ||
|
lower.includes("is required") ||
|
||||||
lower.includes("already") ||
|
lower.includes("already") ||
|
||||||
lower.includes("invalid") ||
|
lower.includes("invalid") ||
|
||||||
lower.includes("accepts only")
|
lower.includes("accepts only") ||
|
||||||
|
lower.includes("must be")
|
||||||
) {
|
) {
|
||||||
return { statusCode: 400, code: "bad_request", message };
|
return { statusCode: 400, code: "bad_request", message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function registerTeamsRoutes(
|
|||||||
const { orgSlug } = request.params as { orgSlug: string };
|
const { orgSlug } = request.params as { orgSlug: string };
|
||||||
const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
|
const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
|
||||||
if (auth === null) return;
|
if (auth === null) return;
|
||||||
const body = request.body as { slug?: unknown; name?: unknown; description?: unknown };
|
const body = (request.body ?? {}) as { slug?: unknown; name?: unknown; description?: unknown };
|
||||||
if (typeof body.slug !== "string" || typeof body.name !== "string") {
|
if (typeof body.slug !== "string" || typeof body.name !== "string") {
|
||||||
return reply.status(400).send({
|
return reply.status(400).send({
|
||||||
error: { code: "bad_request", message: "slug and name are required" },
|
error: { code: "bad_request", message: "slug and name are required" },
|
||||||
|
|||||||
Reference in New Issue
Block a user