154 lines
5.7 KiB
TypeScript
154 lines
5.7 KiB
TypeScript
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
|
|
async function fetchWithAuth(
|
|
url: string,
|
|
options: RequestInit = {},
|
|
token?: string
|
|
) {
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
...((options.headers as Record<string, string>) || {}),
|
|
};
|
|
if (token) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
const res = await fetch(`${API_BASE}${url}`, { ...options, headers });
|
|
|
|
if (res.status === 401) {
|
|
if (typeof window !== "undefined") {
|
|
window.location.href = "/login";
|
|
}
|
|
throw new Error("登录已过期,请重新登录");
|
|
}
|
|
|
|
if (!res.ok) {
|
|
let errorDetail = `请求失败 (HTTP ${res.status})`;
|
|
try {
|
|
const error = await res.json();
|
|
errorDetail = error.detail || error.message || errorDetail;
|
|
} catch {
|
|
// 解析失败,使用默认错误信息
|
|
}
|
|
throw new Error(errorDetail);
|
|
}
|
|
|
|
if (res.status === 204) {
|
|
return null;
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
export const api = {
|
|
auth: {
|
|
register: (data: { name: string; email: string; password: string }) =>
|
|
fetchWithAuth("/api/v1/auth/register", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
}),
|
|
login: (data: { email: string; password: string }) =>
|
|
fetchWithAuth("/api/v1/auth/login", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
}),
|
|
getMe: (token: string) => fetchWithAuth("/api/v1/auth/me", {}, token),
|
|
forgotPassword: (email: string) =>
|
|
fetchWithAuth("/api/v1/auth/forgot-password", {
|
|
method: "POST",
|
|
body: JSON.stringify({ email }),
|
|
}),
|
|
resetPassword: (token: string, newPassword: string) =>
|
|
fetchWithAuth("/api/v1/auth/reset-password", {
|
|
method: "POST",
|
|
body: JSON.stringify({ token, new_password: newPassword }),
|
|
}),
|
|
verifyEmail: (email: string, code: string) =>
|
|
fetchWithAuth("/api/v1/auth/verify-email", {
|
|
method: "POST",
|
|
body: JSON.stringify({ email, code }),
|
|
}),
|
|
resendVerification: (email: string) =>
|
|
fetchWithAuth("/api/v1/auth/resend-verification", {
|
|
method: "POST",
|
|
body: JSON.stringify({ email }),
|
|
}),
|
|
changePassword: (token: string, oldPassword: string, newPassword: string) =>
|
|
fetchWithAuth("/api/v1/auth/change-password", {
|
|
method: "PUT",
|
|
body: JSON.stringify({ old_password: oldPassword, new_password: newPassword }),
|
|
}, token),
|
|
updateProfile: (token: string, data: { name?: string; avatar_url?: string }) =>
|
|
fetchWithAuth("/api/v1/auth/profile", {
|
|
method: "PUT",
|
|
body: JSON.stringify(data),
|
|
}, token),
|
|
},
|
|
queries: {
|
|
list: (token: string) => fetchWithAuth("/api/v1/queries/", {}, token),
|
|
create: (token: string, data: unknown) =>
|
|
fetchWithAuth("/api/v1/queries/", { method: "POST", body: JSON.stringify(data) }, token),
|
|
update: (token: string, id: string, data: unknown) =>
|
|
fetchWithAuth(`/api/v1/queries/${id}`, { method: "PUT", body: JSON.stringify(data) }, token),
|
|
delete: (token: string, id: string) =>
|
|
fetchWithAuth(`/api/v1/queries/${id}`, { method: "DELETE" }, token),
|
|
runNow: (token: string, id: string) =>
|
|
fetchWithAuth(`/api/v1/queries/${id}/run-now`, { method: "POST" }, token),
|
|
},
|
|
citations: {
|
|
list: (token: string, params?: string) =>
|
|
fetchWithAuth(`/api/v1/citations/${params ? `?${params}` : ""}`, {}, token),
|
|
getStats: (token: string) => fetchWithAuth("/api/v1/citations/stats/", {}, token),
|
|
},
|
|
subscriptions: {
|
|
getPlans: async () => {
|
|
const res = await fetch(`${API_BASE}/api/v1/subscriptions/plans`);
|
|
if (!res.ok) throw new Error("获取套餐失败");
|
|
return res.json();
|
|
},
|
|
getCurrent: async (token: string) =>
|
|
fetchWithAuth("/api/v1/subscriptions/current", {}, token),
|
|
subscribe: async (token: string, plan: string) =>
|
|
fetchWithAuth("/api/v1/subscriptions/subscribe", {
|
|
method: "POST",
|
|
body: JSON.stringify({ plan }),
|
|
}, token),
|
|
cancel: async (token: string) =>
|
|
fetchWithAuth("/api/v1/subscriptions/cancel", { method: "POST" }, token),
|
|
getHistory: async (token: string) =>
|
|
fetchWithAuth("/api/v1/subscriptions/history", {}, token),
|
|
},
|
|
admin: {
|
|
getStats: async (token: string) =>
|
|
fetchWithAuth("/api/v1/admin/stats", {}, token),
|
|
getUsers: async (token: string, params?: { skip?: number; limit?: number; search?: string }) => {
|
|
const query = params
|
|
? "?" + new URLSearchParams(Object.entries(params).filter(([, v]) => v !== undefined) as [string, string][]).toString()
|
|
: "";
|
|
return fetchWithAuth(`/api/v1/admin/users${query}`, {}, token);
|
|
},
|
|
getUserDetail: async (token: string, userId: string) =>
|
|
fetchWithAuth(`/api/v1/admin/users/${userId}`, {}, token),
|
|
toggleUserActive: async (token: string, userId: string) =>
|
|
fetchWithAuth(`/api/v1/admin/users/${userId}/toggle-active`, { method: "POST" }, token),
|
|
updateUserPlan: async (token: string, userId: string, plan: string) =>
|
|
fetchWithAuth(`/api/v1/admin/users/${userId}/update-plan`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ plan }),
|
|
}, token),
|
|
},
|
|
reports: {
|
|
exportCSV: (token: string, queryId?: string) => {
|
|
const query = queryId ? `?query_id=${queryId}` : "";
|
|
return fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, token);
|
|
},
|
|
exportPDF: async (token: string, queryId?: string) => {
|
|
const query = queryId ? `?query_id=${queryId}` : "";
|
|
const res = await fetch(`${API_BASE}/api/v1/reports/export/pdf${query}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) throw new Error("导出失败");
|
|
return res.blob();
|
|
},
|
|
},
|
|
};
|