Files
scriptforge/frontend/src/lib/api.ts
zhilv 5414c9c865 feat: 分类/变体体系 + 用户认证 + 管理后台
- 运行时分类体系:Shell/Python/JavaScript/Ruby/PHP 各含变体
- 用户注册/登录(JWT + bcrypt),首个注册用户为管理员
- 管理后台 /admin 动态管理分类和变体
- 脚本市场支持按分类筛选
- CodeMirror 语言模式根据分类名称自动切换
- 结果页展示该分类下所有变体的运行命令
- source 命令变体用于 Shell 类继承环境变量

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:02:20 +08:00

153 lines
5.0 KiB
TypeScript

import { CreateScriptResponse, ScriptDetail, ExpiresIn, MarketResponse, RuntimeCategory, RuntimeVariant, AuthResponse } from '../types'
async function request<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
...options,
})
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }))
throw new Error(body.error || `HTTP ${res.status}`)
}
return res.json()
}
function getToken(): string {
return localStorage.getItem('token') || ''
}
export async function createScript(params: {
title: string
description?: string
content: string
category_id: number
expires_in: ExpiresIn
publish: boolean
}): Promise<CreateScriptResponse> {
return request('/api/scripts', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify(params),
})
}
export async function getScript(id: string): Promise<ScriptDetail> {
return request(`/api/scripts/${id}`)
}
export async function publishScript(id: string, token: string): Promise<{ id: string; status: string }> {
return request(`/api/scripts/${id}/publish?token=${encodeURIComponent(token)}`, {
method: 'POST',
})
}
export async function deleteScript(id: string, token: string): Promise<void> {
await fetch(`/api/scripts/${id}?token=${encodeURIComponent(token)}`, {
method: 'DELETE',
}).then((res) => {
if (!res.ok && res.status !== 204) throw new Error('delete failed')
})
}
export async function listMarket(params: {
page?: number
per_page?: number
category_id?: number
search?: string
}): Promise<MarketResponse> {
const query = new URLSearchParams()
if (params.page) query.set('page', String(params.page))
if (params.per_page) query.set('per_page', String(params.per_page))
if (params.category_id) query.set('category_id', String(params.category_id))
if (params.search) query.set('search', params.search)
return request(`/api/market?${query.toString()}`)
}
export async function listCategories(): Promise<RuntimeCategory[]> {
const res = await request<{ categories: RuntimeCategory[] }>('/api/categories')
return res.categories || []
}
export async function register(username: string, password: string): Promise<AuthResponse> {
const res = await request<AuthResponse>('/api/auth/register', {
method: 'POST',
body: JSON.stringify({ username, password }),
})
localStorage.setItem('token', res.token)
return res
}
export async function login(username: string, password: string): Promise<AuthResponse> {
const res = await request<AuthResponse>('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
})
localStorage.setItem('token', res.token)
return res
}
export async function getMe(): Promise<{ id: number; username: string; role: string }> {
return request('/api/auth/me', {
headers: { 'Authorization': `Bearer ${getToken()}` },
})
}
export function isLoggedIn(): boolean {
return !!localStorage.getItem('token')
}
export function logout(): void {
localStorage.removeItem('token')
}
// --- Admin API ---
export async function adminCreateCategory(cat: Partial<RuntimeCategory>): Promise<RuntimeCategory> {
const res = await request<{ category: RuntimeCategory }>('/api/admin/categories', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify(cat),
})
return res.category
}
export async function adminUpdateCategory(id: number, cat: Partial<RuntimeCategory>): Promise<RuntimeCategory> {
const res = await request<{ category: RuntimeCategory }>(`/api/admin/categories/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify(cat),
})
return res.category
}
export async function adminDeleteCategory(id: number): Promise<void> {
await request(`/api/admin/categories/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${getToken()}` },
})
}
export async function adminCreateVariant(categoryId: number, variant: Partial<RuntimeVariant>): Promise<RuntimeVariant> {
const res = await request<{ variant: RuntimeVariant }>(`/api/admin/categories/${categoryId}/variants`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify(variant),
})
return res.variant
}
export async function adminUpdateVariant(id: number, variant: Partial<RuntimeVariant>): Promise<RuntimeVariant> {
const res = await request<{ variant: RuntimeVariant }>(`/api/admin/variants/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify(variant),
})
return res.variant
}
export async function adminDeleteVariant(id: number): Promise<void> {
await request(`/api/admin/variants/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${getToken()}` },
})
}