refactor: 代码审查修复 — 错误边界、DRY抽取、测试修复、合规性改进

- P1: server.ts 统一错误边界 (withErrorHandler + AppError),修复 3 个失败/卡死测试
- P2: db 层 wrap/paginateQuery 抽取,前端 handleResponse 抽取,parseIdFromUrl 抽取
- P3: middleware 验证消息中文化,Flex→Space 替换
- P0: docs/development/README.md 新增已知设计决策章节
- P3-11 setup 拆分已尝试回退(@testing-library/react preload 依赖无法拆分)
- P3-13 config 层测试从本次变更移除
This commit is contained in:
2026-05-29 22:27:56 +08:00
parent 34e915ccf4
commit 10b3928bee
26 changed files with 428 additions and 300 deletions

View File

@@ -11,6 +11,8 @@ import type {
UpdateModelRequest,
} from "../../shared/api";
import { handleResponse, handleVoidResponse } from "../utils/api";
const MODELS_KEY = ["models"] as const;
export async function createModel(data: CreateModelRequest): Promise<Model> {
@@ -19,20 +21,17 @@ export async function createModel(data: CreateModelRequest): Promise<Model> {
headers: { "Content-Type": "application/json" },
method: "POST",
});
return handleResponse(response);
return handleResponse(response, (data) => (data as ModelResponse).model);
}
export async function deleteModel(id: string): Promise<void> {
const response = await fetch(`/api/models/${id}`, { method: "DELETE" });
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return handleVoidResponse(response);
}
export async function fetchModel(id: string): Promise<Model> {
const response = await fetch(`/api/models/${id}`);
return handleResponse(response);
return handleResponse(response, (data) => (data as ModelResponse).model);
}
export async function fetchModelList(params: {
@@ -76,7 +75,7 @@ export async function updateModel(id: string, data: UpdateModelRequest): Promise
headers: { "Content-Type": "application/json" },
method: "PATCH",
});
return handleResponse(response);
return handleResponse(response, (data) => (data as ModelResponse).model);
}
export function useCreateModel() {
@@ -129,12 +128,3 @@ export function useUpdateModel() {
},
});
}
async function handleResponse(response: Response): Promise<Model> {
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const data = (await response.json()) as ModelResponse;
return data.model;
}

View File

@@ -9,16 +9,13 @@ import type {
UpdateProjectRequest,
} from "../../shared/api";
import { handleResponse, handleVoidResponse } from "../utils/api";
const PROJECTS_KEY = ["projects"] as const;
export async function archiveProject(id: string): Promise<Project> {
const response = await fetch(`/api/projects/${id}/archive`, { method: "POST" });
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const data = (await response.json()) as ProjectResponse;
return data.project;
return handleResponse(response, (data) => (data as ProjectResponse).project);
}
export async function createProject(data: CreateProjectRequest): Promise<Project> {
@@ -27,30 +24,17 @@ export async function createProject(data: CreateProjectRequest): Promise<Project
headers: { "Content-Type": "application/json" },
method: "POST",
});
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const result = (await response.json()) as ProjectResponse;
return result.project;
return handleResponse(response, (data) => (data as ProjectResponse).project);
}
export async function deleteProject(id: string): Promise<void> {
const response = await fetch(`/api/projects/${id}`, { method: "DELETE" });
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return handleVoidResponse(response);
}
export async function fetchProject(id: string): Promise<Project> {
const response = await fetch(`/api/projects/${id}`);
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const data = (await response.json()) as ProjectResponse;
return data.project;
return handleResponse(response, (data) => (data as ProjectResponse).project);
}
export async function fetchProjectList(params: {
@@ -76,12 +60,7 @@ export async function fetchProjectList(params: {
export async function restoreProject(id: string): Promise<Project> {
const response = await fetch(`/api/projects/${id}/restore`, { method: "POST" });
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const data = (await response.json()) as ProjectResponse;
return data.project;
return handleResponse(response, (data) => (data as ProjectResponse).project);
}
export async function updateProject(id: string, data: UpdateProjectRequest): Promise<Project> {
@@ -90,12 +69,7 @@ export async function updateProject(id: string, data: UpdateProjectRequest): Pro
headers: { "Content-Type": "application/json" },
method: "PATCH",
});
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const result = (await response.json()) as ProjectResponse;
return result.project;
return handleResponse(response, (data) => (data as ProjectResponse).project);
}
export function useArchiveProject() {

View File

@@ -11,6 +11,8 @@ import type {
UpdateProviderRequest,
} from "../../shared/api";
import { handleResponse, handleVoidResponse } from "../utils/api";
const PROVIDERS_KEY = ["providers"] as const;
const MODELS_KEY = ["models"] as const;
@@ -20,20 +22,17 @@ export async function createProvider(data: CreateProviderRequest): Promise<Provi
headers: { "Content-Type": "application/json" },
method: "POST",
});
return handleResponse(response);
return handleResponse(response, (data) => (data as ProviderResponse).provider);
}
export async function deleteProvider(id: string): Promise<void> {
const response = await fetch(`/api/providers/${id}`, { method: "DELETE" });
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return handleVoidResponse(response);
}
export async function fetchProvider(id: string): Promise<Provider> {
const response = await fetch(`/api/providers/${id}`);
return handleResponse(response);
return handleResponse(response, (data) => (data as ProviderResponse).provider);
}
export async function fetchProviderList(params: {
@@ -84,7 +83,7 @@ export async function updateProvider(id: string, data: UpdateProviderRequest): P
headers: { "Content-Type": "application/json" },
method: "PATCH",
});
return handleResponse(response);
return handleResponse(response, (data) => (data as ProviderResponse).provider);
}
export function useCreateProvider() {
@@ -145,12 +144,3 @@ export function useUpdateProvider() {
},
});
}
async function handleResponse(response: Response): Promise<Provider> {
if (!response.ok) {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
const data = (await response.json()) as ProviderResponse;
return data.provider;
}