- bzzoiro events/standings/stats 抓取失败写入 IngestFailure 死信(尽力而为, 写入失败不影响主流程);顺带修复 standings 失败路径 league_r 缺 errors 键 的 KeyError —— 该路径此前从未跑通,一旦失败会顶掉原始异常 - admin/api.ts 收敛为 lib/http.ts 薄门面,消除第二套 HTTP 实现; UNAUTHORIZED_EVENT 定义移至共享层,断开 lib→admin 反向依赖 - STATUS_META 死键 live 改为 in_play(对齐 normalize.py 口径), 补 paused/postponed/cancelled/suspended;移除无人消费的 DashboardStats.total_matches(items.length 近似,上限 100) - 新增 tests/test_ingest_deadletter.py(6 例,变异验证判别力)
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
/**
|
|
* Admin 后台管理系统 - 统一 API 客户端(门面)
|
|
*
|
|
* 鉴权:通过 POST /api/v1/auth/login 用密码换取 HttpOnly Cookie 会话,
|
|
* 同源请求自动携带 Cookie,无需手动管理密钥。
|
|
* 收到 401 时广播 `profeto:unauthorized` 事件,由 AdminLayout 切回登录页。
|
|
*
|
|
* 实现已收敛到共享层 lib/http.ts(超时/错误解析/401 广播只此一份),
|
|
* 本文件仅保留 Admin 侧的门面签名与认证接口,供既有页面按原路径导入。
|
|
*/
|
|
|
|
import { http, ApiError, UNAUTHORIZED_EVENT } from '../lib/http'
|
|
|
|
/** Admin 侧兼容导出:错误类型与会话失效事件名的规范来源在 lib/http */
|
|
export { ApiError, UNAUTHORIZED_EVENT }
|
|
|
|
const API_BASE = '/api/v1'
|
|
|
|
/** Admin 请求可覆盖项(与 lib/http RequestOptions 对齐的子集) */
|
|
type ApiOpts = {
|
|
timeoutMs?: number
|
|
/** 改密接口的 401 表示「当前密码错误」,非会话过期,置 true 跳过登出广播 */
|
|
skipAuthHandling?: boolean
|
|
}
|
|
|
|
export const api = {
|
|
get: <T>(path: string) => http.get<T>(path),
|
|
post: <T>(path: string, body?: unknown, opts?: ApiOpts) =>
|
|
http.post<T>(path, body, opts),
|
|
put: <T>(path: string, body?: unknown, opts?: ApiOpts) =>
|
|
http.put<T>(path, body, opts),
|
|
delete: <T>(path: string) => http.delete<T>(path),
|
|
}
|
|
|
|
// ── 认证 ────────────────────────────────────────────────────────
|
|
|
|
/** 密码登录,成功后服务端写入 HttpOnly 会话 Cookie */
|
|
export function login(password: string): Promise<{ ok: boolean }> {
|
|
return api.post(`${API_BASE}/auth/login`, { password })
|
|
}
|
|
|
|
/** 退出登录,清除会话 Cookie */
|
|
export function logout(): Promise<{ ok: boolean }> {
|
|
return api.post(`${API_BASE}/auth/logout`)
|
|
}
|
|
|
|
/** 探测当前登录状态 */
|
|
export function fetchAuthState(): Promise<{
|
|
authenticated: boolean
|
|
enabled: boolean
|
|
password_origin?: 'db' | 'env' | 'none'
|
|
}> {
|
|
return api.get(`${API_BASE}/auth/me`)
|
|
}
|
|
|
|
/** 修改管理员密码(成功后所有会话失效,需重新登录) */
|
|
export function changePassword(currentPassword: string, newPassword: string): Promise<{ ok: boolean; message: string }> {
|
|
// skipAuthHandling: 改密接口的 401 表示「当前密码错误」,非会话过期,不要触发登出
|
|
return api.post(
|
|
`${API_BASE}/auth/change-password`,
|
|
{ current_password: currentPassword, new_password: newPassword },
|
|
{ skipAuthHandling: true },
|
|
)
|
|
}
|
|
|
|
export { API_BASE }
|