fix(api): resolve team name lookup and show server error messages

- Backend: look up teams by name first (frontend sends name, not ID)
- Frontend: parse response body on API errors instead of showing
  generic "API error: 400" — now shows the actual server message
  (e.g. "Password must be at least 8 characters...")
This commit is contained in:
Siddharth Kumar Sah
2026-03-27 16:41:44 +08:00
parent b5721d9854
commit 620b8ad038
2 changed files with 36 additions and 18 deletions
+20 -14
View File
@@ -369,27 +369,33 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
const role = body.role === "admin" ? "admin" : "user"; const role = body.role === "admin" ? "admin" : "user";
// Look up Default team ID // Resolve team — frontend sends team name (e.g. "Default"), not ID
const defaultTeam = db const requestedTeam = (body as { team?: string }).team;
.select() let team: string;
.from(schema.teams)
.where(eq(schema.teams.name, "Default"))
.get();
const teamId = (body as { team?: string }).team || defaultTeam?.id || "default-team-00000000";
// If a specific team was provided, validate it exists if (requestedTeam) {
if ((body as { team?: string }).team) { // Look up by name first, then fall back to ID
const teamExists = db const teamByName = db
.select() .select()
.from(schema.teams) .from(schema.teams)
.where(eq(schema.teams.id, (body as { team?: string }).team ?? "")) .where(eq(schema.teams.name, requestedTeam))
.get(); .get();
if (!teamExists) const teamById = teamByName
? null
: db.select().from(schema.teams).where(eq(schema.teams.id, requestedTeam)).get();
const found = teamByName || teamById;
if (!found)
return reply.status(400).send({ error: "Team not found", code: "VALIDATION_ERROR" }); return reply.status(400).send({ error: "Team not found", code: "VALIDATION_ERROR" });
team = found.id;
} else {
const defaultTeam = db
.select()
.from(schema.teams)
.where(eq(schema.teams.name, "Default"))
.get();
team = defaultTeam?.id || "default-team-00000000";
} }
const team = teamId;
// Check for duplicate username first (so 409 takes priority over limit) // Check for duplicate username first (so 409 takes priority over limit)
const existing = db const existing = db
.select() .select()
+16 -4
View File
@@ -1,10 +1,22 @@
const API_BASE = "/api"; const API_BASE = "/api";
async function throwWithMessage(res: Response): Promise<never> {
let msg = `API error: ${res.status}`;
try {
const body = await res.json();
if (body.error) msg = body.error;
else if (body.message) msg = body.message;
} catch {
// response wasn't JSON — use the default message
}
throw new Error(msg);
}
export async function apiGet<T>(path: string): Promise<T> { export async function apiGet<T>(path: string): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, { const res = await fetch(`${API_BASE}${path}`, {
headers: { Authorization: `Bearer ${getToken()}` }, headers: { Authorization: `Bearer ${getToken()}` },
}); });
if (!res.ok) throw new Error(`API error: ${res.status}`); if (!res.ok) await throwWithMessage(res);
return res.json(); return res.json();
} }
@@ -17,7 +29,7 @@ export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
}, },
body: body ? JSON.stringify(body) : undefined, body: body ? JSON.stringify(body) : undefined,
}); });
if (!res.ok) throw new Error(`API error: ${res.status}`); if (!res.ok) await throwWithMessage(res);
return res.json(); return res.json();
} }
@@ -30,7 +42,7 @@ export async function apiPut<T>(path: string, body?: unknown): Promise<T> {
}, },
body: body ? JSON.stringify(body) : undefined, body: body ? JSON.stringify(body) : undefined,
}); });
if (!res.ok) throw new Error(`API error: ${res.status}`); if (!res.ok) await throwWithMessage(res);
return res.json(); return res.json();
} }
@@ -41,7 +53,7 @@ export async function apiDelete<T>(path: string): Promise<T> {
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${getToken()}`,
}, },
}); });
if (!res.ok) throw new Error(`API error: ${res.status}`); if (!res.ok) await throwWithMessage(res);
return res.json(); return res.json();
} }