实现登录注册与模型大厅、预览视图
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// Lightweight API client for Basic Auth backend
|
||||
// Base URL can be configured via Vite env VITE_API_BASE, defaults to '/api'
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
username: string
|
||||
}
|
||||
|
||||
export type Me = User & {
|
||||
avatarUrl?: string
|
||||
}
|
||||
|
||||
export type ModelSummary = {
|
||||
id: string
|
||||
title: string
|
||||
desc?: string
|
||||
authorId: string
|
||||
authorName?: string
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export type ModelDetail = ModelSummary & {
|
||||
model: any
|
||||
}
|
||||
|
||||
export type ListModelsQuery = {
|
||||
q?: string
|
||||
authorId?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
sort?: 'new' | 'old'
|
||||
}
|
||||
|
||||
const API_BASE: string = (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_API_BASE) || '/api'
|
||||
|
||||
function buildAuthHeader(basicToken?: string): Record<string, string> {
|
||||
if (!basicToken) return {}
|
||||
return { Authorization: `Basic ${basicToken}` }
|
||||
}
|
||||
|
||||
async function handle<T>(res: Response): Promise<T> {
|
||||
if (!res.ok) {
|
||||
let msg = res.status + ' ' + res.statusText
|
||||
try {
|
||||
const data = await res.json()
|
||||
if (data && data.message) msg = data.message
|
||||
} catch {}
|
||||
throw new Error(msg)
|
||||
}
|
||||
const ct = res.headers.get('content-type') || ''
|
||||
if (ct.includes('application/json')) return (await res.json()) as T
|
||||
return (await res.text()) as unknown as T
|
||||
}
|
||||
|
||||
export const api = {
|
||||
base: API_BASE,
|
||||
// Users
|
||||
async register(username: string, password: string) {
|
||||
const res = await fetch(`${API_BASE}/users/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
return handle<User>(res)
|
||||
},
|
||||
async me(basicToken?: string) {
|
||||
const res = await fetch(`${API_BASE}/users/me`, {
|
||||
headers: { ...buildAuthHeader(basicToken) },
|
||||
})
|
||||
return handle<Me>(res)
|
||||
},
|
||||
async updateMe(payload: { username?: string; avatarBase64?: string; avatarMime?: string }, basicToken?: string) {
|
||||
const res = await fetch(`${API_BASE}/users/me`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
return handle<Me>(res)
|
||||
},
|
||||
avatarUrl(userId: string) {
|
||||
return `${API_BASE}/users/${encodeURIComponent(userId)}/avatar`
|
||||
},
|
||||
|
||||
// Models
|
||||
async createModel(
|
||||
payload: { title: string; desc?: string; model: any; previewBase64?: string; previewMime?: string },
|
||||
basicToken?: string
|
||||
) {
|
||||
const res = await fetch(`${API_BASE}/models`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
return handle<ModelDetail>(res)
|
||||
},
|
||||
async updateModel(id: string, payload: Partial<{ title: string; desc: string; model: any; previewBase64: string; previewMime: string }>, basicToken?: string) {
|
||||
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
return handle<ModelDetail>(res)
|
||||
},
|
||||
async deleteModel(id: string, basicToken?: string) {
|
||||
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
headers: { ...buildAuthHeader(basicToken) },
|
||||
})
|
||||
return handle<{ ok: true }>(res)
|
||||
},
|
||||
async getModel(id: string) {
|
||||
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`)
|
||||
return handle<ModelDetail>(res)
|
||||
},
|
||||
async listModels(query: ListModelsQuery = {}) {
|
||||
const usp = new URLSearchParams()
|
||||
if (query.q) usp.set('q', query.q)
|
||||
if (query.authorId) usp.set('authorId', query.authorId)
|
||||
if (query.page) usp.set('page', String(query.page))
|
||||
if (query.pageSize) usp.set('pageSize', String(query.pageSize))
|
||||
if (query.sort) usp.set('sort', query.sort)
|
||||
const res = await fetch(`${API_BASE}/models${usp.toString() ? '?' + usp.toString() : ''}`)
|
||||
return handle<{ items: ModelSummary[]; total?: number; page?: number; pageSize?: number }>(res)
|
||||
},
|
||||
async downloadModelJson(id: string) {
|
||||
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}/model.json`)
|
||||
return handle<any>(res)
|
||||
},
|
||||
previewUrl(id: string) {
|
||||
return `${API_BASE}/models/${encodeURIComponent(id)}/preview`
|
||||
},
|
||||
}
|
||||
|
||||
export type BasicCredential = { username: string; password: string }
|
||||
export function encodeBasic({ username, password }: BasicCredential) {
|
||||
return btoa(`${username}:${password}`)
|
||||
}
|
||||
Reference in New Issue
Block a user