- 运行时分类体系:Shell/Python/JavaScript/Ruby/PHP 各含变体 - 用户注册/登录(JWT + bcrypt),首个注册用户为管理员 - 管理后台 /admin 动态管理分类和变体 - 脚本市场支持按分类筛选 - CodeMirror 语言模式根据分类名称自动切换 - 结果页展示该分类下所有变体的运行命令 - source 命令变体用于 Shell 类继承环境变量 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
153 lines
5.0 KiB
TypeScript
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()}` },
|
|
})
|
|
} |