Files
plainleaf/client/spaces_ui/api.ts
T
wushenghuaandCodex db73584e2b
CI / config (push) Canceled after 0s
CI / test-frontend (push) Canceled after 0s
CI / test-rust (push) Canceled after 0s
CI / test-e2e (push) Canceled after 0s
CI / test-e2e-release (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / release (push) Canceled after 0s
CI / docker (push) Canceled after 0s
CI / docker-website (push) Canceled after 0s
feat: 完善账号管理与扩展体验
补齐普通用户自助账号、中文设置与快捷键提示,修复大型扩展安装和移动端弹窗,并按服务器能力限制不兼容扩展。

Co-Authored-By: Codex <noreply@anthropic.com>
2026-08-12 22:13:23 +08:00

140 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { RuntimeAvailability } from "./runtime_availability.ts";
import type { FieldError, UserInfo } from "./types.ts";
export async function api(
method: string,
path: string,
body?: unknown,
): Promise<any> {
const resp = await fetch(path, {
method,
headers: body ? { "Content-Type": "application/json" } : {},
body: body ? JSON.stringify(body) : undefined,
});
// Only 401 — "no valid session" — means the browser should go log in.
// A 403 is "signed in, but not permitted": it falls through to the normal
// error path below so the caller renders a message. Redirecting on 403
// would loop, since the login screen bounces a valid session straight back.
if (resp.status === 401) throw { unauthorized: true };
const json = await resp.json().catch(() => ({}));
if (!resp.ok) {
const errors = json.errors ?? [
{
field: "",
message:
resp.status === 403
? "没有权限执行此操作"
: `请求失败(HTTP ${resp.status})`,
},
];
if (resp.status === 404) errors.notFound = true;
throw errors;
}
return json;
}
/**
* Admin-only endpoints live under `api/admin/`. Split from `api()` so a
* mistaken call to an admin route from a non-admin screen is visible at the
* call site rather than buried in a path string.
*/
export function adminApi(
method: string,
path: string,
body?: unknown,
): Promise<any> {
return api(method, `api/admin/${path}`, body);
}
export function formatApiError(e: unknown): string {
if (Array.isArray(e)) {
return e
.map((fe: FieldError) =>
fe.field ? `${fe.field}: ${fe.message}` : fe.message,
)
.join(", ");
}
return "请求失败";
}
/** Server-level facts for admin screens. See `RuntimeAvailability`. */
export function getServerInfo(): Promise<{ runtimeApi: RuntimeAvailability }> {
return adminApi("GET", "server-info");
}
// --- User management (backed by users.json via the admin API) ------------
export function listUsers(): Promise<Record<string, UserInfo>> {
return adminApi("GET", "users");
}
export function getUser(name: string): Promise<UserInfo> {
return adminApi("GET", `users/${encodeURIComponent(name)}`);
}
export function getSession(): Promise<{ username: string; admin: boolean }> {
return api("GET", "api/session");
}
export function getOwnAccount(): Promise<{
username: string;
user: UserInfo;
}> {
return api("GET", "api/account");
}
export function changeOwnPassword(
currentPassword: string,
newPassword: string,
): Promise<void> {
return api("POST", "api/account/password", {
currentPassword,
newPassword,
});
}
export async function createOwnToken(name: string): Promise<string> {
const result = await api("POST", "api/account/tokens", { name });
return result.token;
}
export function deleteOwnToken(name: string): Promise<void> {
return api("DELETE", `api/account/tokens/${encodeURIComponent(name)}`);
}
export function createUser(
username: string,
password: string,
admin: boolean,
): Promise<void> {
return adminApi("POST", "users", { username, password, admin });
}
export function deleteUser(name: string): Promise<void> {
return adminApi("DELETE", `users/${encodeURIComponent(name)}`);
}
export function setUserPassword(name: string, password: string): Promise<void> {
return adminApi("POST", `users/${encodeURIComponent(name)}/password`, {
password,
});
}
export function setUserAdmin(name: string, admin: boolean): Promise<void> {
return adminApi("PUT", `users/${encodeURIComponent(name)}`, { admin });
}
export async function createToken(user: string, name: string): Promise<string> {
const r = await adminApi("POST", `users/${encodeURIComponent(user)}/tokens`, {
name,
});
return r.token;
}
export function deleteToken(user: string, name: string): Promise<void> {
return adminApi(
"DELETE",
`users/${encodeURIComponent(user)}/tokens/${encodeURIComponent(name)}`,
);
}