81d6b377c7
Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
3.5 KiB
TypeScript
143 lines
3.5 KiB
TypeScript
export type TTLOption = '1h' | '24h' | '7d' | 'never'
|
|
|
|
export interface Item {
|
|
slug: string
|
|
kind: string
|
|
title: string
|
|
body?: string | null
|
|
file_name?: string | null
|
|
mime?: string | null
|
|
size_bytes?: number | null
|
|
is_public: boolean
|
|
burn_after_read: boolean
|
|
expires_at: string | null
|
|
created_at: string
|
|
view_count: number
|
|
id?: number
|
|
created_ip?: string
|
|
burned?: boolean
|
|
}
|
|
|
|
export interface WallResponse {
|
|
items: Item[]
|
|
page: number
|
|
page_size: number
|
|
total: number
|
|
}
|
|
|
|
export interface Ban {
|
|
ip: string
|
|
reason: string
|
|
created_at: string
|
|
}
|
|
|
|
async function parseError(res: Response): Promise<string> {
|
|
try {
|
|
const data = await res.json()
|
|
if (typeof data?.detail === 'string') return data.detail
|
|
return JSON.stringify(data?.detail ?? data)
|
|
} catch {
|
|
return res.statusText || `HTTP ${res.status}`
|
|
}
|
|
}
|
|
|
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(path, init)
|
|
if (!res.ok) {
|
|
throw new Error(await parseError(res))
|
|
}
|
|
if (res.status === 204) {
|
|
return undefined as T
|
|
}
|
|
return (await res.json()) as T
|
|
}
|
|
|
|
export function createItem(payload: {
|
|
body: string
|
|
title?: string
|
|
is_public: boolean
|
|
burn_after_read: boolean
|
|
ttl: TTLOption
|
|
}): Promise<Item> {
|
|
return request<Item>('/api/items', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export function uploadItem(form: FormData): Promise<Item> {
|
|
return request<Item>('/api/items/upload', {
|
|
method: 'POST',
|
|
body: form,
|
|
})
|
|
}
|
|
|
|
export function fetchWall(page = 1, pageSize = 20): Promise<WallResponse> {
|
|
const q = new URLSearchParams({
|
|
page: String(page),
|
|
page_size: String(pageSize),
|
|
})
|
|
return request<WallResponse>(`/api/items/wall?${q}`)
|
|
}
|
|
|
|
export function fetchItem(slug: string): Promise<Item> {
|
|
return request<Item>(`/api/items/${encodeURIComponent(slug)}`)
|
|
}
|
|
|
|
export function itemFileUrl(slug: string): string {
|
|
return `/api/items/${encodeURIComponent(slug)}/file`
|
|
}
|
|
|
|
export function adminLogin(password: string): Promise<{ token: string }> {
|
|
return request<{ token: string }>('/api/admin/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password }),
|
|
})
|
|
}
|
|
|
|
function authHeaders(token: string): HeadersInit {
|
|
return { Authorization: `Bearer ${token}` }
|
|
}
|
|
|
|
export function adminListItems(token: string): Promise<{ items: Item[] }> {
|
|
return request<{ items: Item[] }>('/api/admin/items', {
|
|
headers: authHeaders(token),
|
|
})
|
|
}
|
|
|
|
export function adminDeleteItem(token: string, itemId: number): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>(`/api/admin/items/${itemId}`, {
|
|
method: 'DELETE',
|
|
headers: authHeaders(token),
|
|
})
|
|
}
|
|
|
|
export function adminListBans(token: string): Promise<{ bans: Ban[] }> {
|
|
return request<{ bans: Ban[] }>('/api/admin/bans', {
|
|
headers: authHeaders(token),
|
|
})
|
|
}
|
|
|
|
export function adminCreateBan(
|
|
token: string,
|
|
payload: { ip: string; reason?: string },
|
|
): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>('/api/admin/bans', {
|
|
method: 'POST',
|
|
headers: {
|
|
...authHeaders(token),
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export function adminDeleteBan(token: string, ip: string): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>(`/api/admin/bans/${encodeURIComponent(ip)}`, {
|
|
method: 'DELETE',
|
|
headers: authHeaders(token),
|
|
})
|
|
}
|