feat: 分类/变体体系 + 用户认证 + 管理后台
- 运行时分类体系:Shell/Python/JavaScript/Ruby/PHP 各含变体 - 用户注册/登录(JWT + bcrypt),首个注册用户为管理员 - 管理后台 /admin 动态管理分类和变体 - 脚本市场支持按分类筛选 - CodeMirror 语言模式根据分类名称自动切换 - 结果页展示该分类下所有变体的运行命令 - source 命令变体用于 Shell 类继承环境变量 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CreateScriptResponse, ScriptDetail, RuntimeOption, ExpiresIn, MarketResponse } from '../types'
|
||||
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, {
|
||||
@@ -12,16 +12,21 @@ async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
||||
return res.json()
|
||||
}
|
||||
|
||||
function getToken(): string {
|
||||
return localStorage.getItem('token') || ''
|
||||
}
|
||||
|
||||
export async function createScript(params: {
|
||||
title: string
|
||||
description?: string
|
||||
content: string
|
||||
runtime: RuntimeOption
|
||||
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),
|
||||
})
|
||||
}
|
||||
@@ -40,22 +45,109 @@ 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')
|
||||
}
|
||||
if (!res.ok && res.status !== 204) throw new Error('delete failed')
|
||||
})
|
||||
}
|
||||
|
||||
export async function listMarket(params: {
|
||||
page?: number
|
||||
per_page?: number
|
||||
runtime?: string
|
||||
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.runtime) query.set('runtime', params.runtime)
|
||||
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()}` },
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user