/** 管理后台 API(对齐 t3 契约:login/verify/logout/file list/update/delete/batch-delete/config/audit) */ import { request } from './http' import { paths } from './paths' import type { AdminConfig, AdminFileListResult, AdminFileUpdatePayload, AdminLoginResult, AuditListResult, } from './types' import { pick } from '@/utils/format' /** 管理员登录(密码错误 → 401 ApiError) */ export function adminLogin(password: string): Promise { return request(paths.adminLogin, { method: 'POST', json: { password } }) } export function adminVerify(): Promise> { return request>(paths.adminVerify) } export async function adminLogout(): Promise { await request(paths.adminLogout, { method: 'POST' }) } export interface AdminFileListQuery { page: number size: number keyword?: string } /** 文件列表(宽松解析:兼容 data 字段或 list 字段、total 顶层) */ export async function adminFileList(q: AdminFileListQuery): Promise { const raw = await request>(paths.adminFileList, { query: { page: q.page, size: q.size, keyword: q.keyword || undefined }, }) const rows = (pick(raw, ['data', 'list', 'items', 'files']) ?? []) as Record[] const total = Number(pick(raw, ['total', 'count']) ?? rows.length) return { page: Number(pick(raw, ['page']) ?? q.page), size: Number(pick(raw, ['size']) ?? q.size), total, data: rows.map((r) => ({ id: Number(pick(r, ['id']) ?? 0), code: String(pick(r, ['code']) ?? ''), name: String(pick(r, ['name']) ?? `${pick(r, ['prefix']) ?? ''}${pick(r, ['suffix']) ?? ''}`), suffix: String(pick(r, ['suffix']) ?? ''), size: Number(pick(r, ['size']) ?? 0), isText: Boolean(pick(r, ['isText', 'is_text']) ?? false), expiredAt: (pick(r, ['expiredAt', 'expired_at', 'expires_at']) as string) ?? null, expiredCount: (pick(r, ['expiredCount', 'expired_count']) as number) ?? null, usedCount: Number(pick(r, ['usedCount', 'used_count']) ?? 0), createdAt: (pick(r, ['createdAt', 'created_at']) as string) ?? null, isExpired: Boolean(pick(r, ['isExpired', 'is_expired']) ?? false), })), } } /** 删除单个文件 */ export async function adminFileDelete(id: number): Promise { await request(paths.adminFileDelete, { method: 'DELETE', json: { id } }) } /** 批量删除(契约:POST /admin/file/batch-delete,body {ids:number[]}) */ export async function adminFileBatchDelete(ids: number[]): Promise { await request(paths.adminFileBatchDelete, { method: 'POST', json: { ids } }) } /** 更新分享(code/过期时间/次数;go-api 定稿 PATCH /admin/file/update) */ export async function adminFileUpdate(payload: AdminFileUpdatePayload): Promise { await request(paths.adminFileUpdate, { method: 'PATCH', json: payload }) } /** 读取管理端配置(KV 全量,含 site_name/logo_url/favicon_url) */ export async function adminConfigGet(): Promise { const raw = await request(paths.adminConfigGet) // 兼容:后端可能直接返回 KV 对象,或包一层 config/data if (raw && typeof raw === 'object' && !Array.isArray(raw)) { const obj = raw as Record const inner = (pick>(obj, ['config', 'data', 'settings']) ?? obj) as Record return inner } return {} } /** 更新管理端配置(PATCH 部分字段 JSON;提交后全站生效,含 site_name/logo_url/favicon_url) */ export async function adminConfigUpdate(patch: Record): Promise { await request(paths.adminConfigUpdate, { method: 'PATCH', json: patch }) } /** v3 存储引擎热切换:成功返回当前引擎名;失败(503)服务端保持原引擎并抛 ApiError */ export async function adminStorageSwitch(engine: 'local' | 's3' | 'webdav'): Promise { const data = await request<{ ok: boolean; engine: string }>(paths.adminStorageSwitch, { method: 'POST', json: { engine }, }) return String(data?.engine ?? engine) } /** 修改管理员密码(成功后旧 token 全部失效,需重新登录) */ export async function adminPasswordUpdate(oldPassword: string, newPassword: string): Promise { await request(paths.adminPasswordUpdate, { method: 'PATCH', json: { old_password: oldPassword, new_password: newPassword }, }) } export interface AuditListQuery { page: number size: number action?: string result?: string ip?: string /** ISO 或 YYYY-MM-DD;由调用方格式化 */ startTime?: string endTime?: string } /** 审计日志列表(宽松解析兼容 data/list 字段与 snake_case/camelCase 行字段) */ export async function adminAuditList(q: AuditListQuery): Promise { const raw = await request>(paths.adminAuditList, { query: { page: q.page, size: q.size, action: q.action || undefined, result: q.result || undefined, ip: q.ip || undefined, start_time: q.startTime || undefined, end_time: q.endTime || undefined, }, }) const rows = (pick(raw, ['data', 'list', 'items', 'logs']) ?? []) as Record[] const total = Number(pick(raw, ['total', 'count']) ?? rows.length) return { page: Number(pick(raw, ['page']) ?? q.page), size: Number(pick(raw, ['size']) ?? q.size), total, data: rows.map((r, idx) => ({ id: Number(pick(r, ['id']) ?? idx + 1), action: String(pick(r, ['action']) ?? ''), result: String(pick(r, ['result']) ?? ''), fileCode: String(pick(r, ['file_code', 'fileCode', 'code']) ?? ''), fileName: String(pick(r, ['file_name', 'fileName', 'name']) ?? ''), sizeBytes: (pick(r, ['size_bytes', 'sizeBytes', 'size']) as number) ?? null, transferredBytes: (pick(r, ['transferred_bytes', 'transferredBytes', 'bytes']) as number) ?? null, ip: String(pick(r, ['ip', 'client_ip', 'clientIp']) ?? ''), userAgent: String(pick(r, ['user_agent', 'userAgent']) ?? ''), deviceOs: String(pick(r, ['device_os', 'deviceOs', 'os']) ?? ''), deviceBrowser: String(pick(r, ['device_browser', 'deviceBrowser', 'browser']) ?? ''), deviceType: String(pick(r, ['device_type', 'deviceType']) ?? ''), actor: String(pick(r, ['actor']) ?? ''), errorMsg: String(pick(r, ['error_msg', 'errorMsg', 'error']) ?? ''), durationMs: (pick(r, ['duration_ms', 'durationMs', 'duration']) as number) ?? null, createdAt: (pick(r, ['created_at', 'createdAt', 'time']) as string) ?? null, })), } }