Files
CloudSearch/source_clean/frontend-src/src/api/index.ts

474 lines
13 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios from 'axios'
import type {
SearchResponse,
VideoParseResult,
SaveResult,
QueryResponse,
RankingItem,
Promotion,
CloudConfig,
StatsData,
} from '../types'
const api = axios.create({
baseURL: '/api',
timeout: 30000,
})
// 请求拦截器 — 添加管理员 Token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('admin_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器 — 统一错误处理
api.interceptors.response.use(
(res) => res,
(err) => {
if (err.response?.status === 401) {
localStorage.removeItem('admin_token')
// Don't redirect if already on the login page or if this was a login attempt itself
if (!window.location.pathname.startsWith('/admin/login') && !err.config?.url?.includes('/admin/login')) {
window.location.href = '/admin/login'
}
}
return Promise.reject(err)
}
)
// ===== 搜索与解析 =====
export async function query(q: string, page = 1): Promise<QueryResponse> {
const { data } = await api.post<QueryResponse>('/query', { q, page })
return data
}
/**
* 流式搜索 — 使用 NDJSON stream逐条返回验证结果
* callback 接收五种事件:
* onSearching() - 搜索开始(立即返回)
* onSaved({results, total}) - 本地已保存资源DB缓存即时返回
* onStats({total, channels, content_info, content_tags}) - 统计信息
* onResult(id, valid, message) - 单条链接验证结果
* onComplete({results, channels, total, filtered}) - 全部完成
*/
export async function streamSearch(
q: string,
callbacks: {
onSearching?: () => void
onSaved?: (data: { results: any[]; total: number }) => void
onStats: (stats: any) => void
onResult: (id: string, valid: boolean, message?: string) => void
onComplete: (data: any) => void
onError?: (err: any) => void
}
): Promise<void> {
const token = localStorage.getItem('admin_token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
try {
const response = await fetch('/api/query', {
method: 'POST',
headers,
body: JSON.stringify({ q }),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const reader = response.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
try {
const msg = JSON.parse(line)
switch (msg.type) {
case 'searching':
callbacks.onSearching?.()
break
case 'saved':
callbacks.onSaved?.(msg)
break
case 'stats':
callbacks.onStats(msg)
break
case 'result':
callbacks.onResult(msg.id, msg.valid, msg.message)
break
case 'complete':
callbacks.onComplete(msg)
break
}
} catch {
// skip malformed lines
}
}
}
} catch (err: any) {
callbacks.onError?.(err)
}
}
export async function searchPanSou(
kw: string,
page = 1,
pageSize = 20
): Promise<SearchResponse> {
const { data } = await api.get<SearchResponse>('/search', {
params: { kw, page, page_size: pageSize },
})
return data
}
export async function parseVideo(url: string): Promise<VideoParseResult> {
const { data } = await api.post<VideoParseResult>('/video/parse', { url })
return data
}
// ===== 保存与分享 =====
export async function saveToCloud(params: {
type: 'search' | 'video'
source: any
target_cloud: string
}): Promise<SaveResult> {
const { data } = await api.post<SaveResult>('/save', params)
return data
}
export async function saveVideoToCloud(params: {
video_url: string
title: string
target_cloud: string
}): Promise<SaveResult> {
const { data } = await api.post<SaveResult>('/video/save-to-cloud', params)
return data
}
// ===== 排行榜 =====
export async function getRankings(): Promise<RankingItem[]> {
const { data } = await api.get<RankingItem[]>('/rankings')
return data
}
export async function getHotKeywords(): Promise<string[]> {
const { data } = await api.get<string[]>('/rankings/hot')
return data
}
export async function getCategorizedRankings(): Promise<any> {
const { data } = await api.get<any>('/rankings/categorized')
return data
}
// ===== 管理员 =====
export async function adminLogin(
username: string,
password: string
): Promise<{ token: string }> {
const { data } = await api.post('/admin/login', { username, password })
return data
}
export async function getMe(): Promise<{ loggedIn: boolean; id?: number; username?: string }> {
const { data } = await api.get('/me')
return data
}
export async function getCloudConfigs(): Promise<CloudConfig[]> {
const { data } = await api.get('/admin/cloud-configs')
return data
}
export async function saveCloudConfig(
config: CloudConfig & { cookie?: string }
): Promise<CloudConfig> {
const { data } = await api.post('/admin/cloud-configs', config)
return data
}
export async function updateCloudConfig(
config: CloudConfig & { cookie?: string }
): Promise<CloudConfig> {
const { data } = await api.put(`/admin/cloud-configs/${config.id}`, config)
return data
}
export async function testCloudConnection(
cloudType: string,
cookie?: string,
id?: number
): Promise<{
success: boolean
message: string
nickname?: string
storage_used?: string
storage_total?: string
}> {
const { data } = await api.post(`/admin/cloud-configs/${cloudType}/test`, { cookie, id })
return data
}
export async function dailyCheckIn(
id: number
): Promise<{
success: boolean
message: string
signedDays?: number
}> {
const { data } = await api.post(`/admin/cloud-configs/${id}/checkin`)
return data
}
export async function skipCheckin(id: number): Promise<boolean> {
const { data } = await api.post(`/admin/cloud-configs/${id}/skip-checkin`)
return data.success
}
export async function checkinAll(): Promise<{
total: number
results: { id: number; nickname: string; success: boolean; message: string }[]
}> {
const { data } = await api.post('/admin/cloud-configs/checkin-all')
return data
}
export async function checkinSummary(): Promise<{
total: number
success: number
failed: number
pending: number
skipped: number
}> {
const { data } = await api.get('/admin/cloud-configs/checkin-summary')
return data
}
export async function deleteCloudConfig(
id: number
): Promise<void> {
await api.delete(`/admin/cloud-configs/${id}`)
}
export async function setPrimary(
id: number,
primary: boolean
): Promise<any> {
const { data } = await api.put(`/admin/cloud-configs/${id}/primary`, { primary })
return data
}
export async function getStats(days?: number): Promise<StatsData> {
const params: Record<string, number> = {}
if (days) params.days = days
const { data } = await api.get('/admin/stats', { params })
return data
}
// ===== 转存日志 =====
export async function getSaveRecords(page = 1, pageSize = 20, startDate?: string, endDate?: string, status?: string, cloud?: string, keyword?: string): Promise<{
total: number
records: SaveRecord[]
summary?: { total: number; success: number; failed: number; reused: number }
}> {
const params: Record<string, number | string> = { page, pageSize }
if (startDate) params.startDate = startDate
if (endDate) params.endDate = endDate
if (status) params.status = status
if (cloud) params.sourceType = cloud
if (keyword) params.keyword = keyword
const { data } = await api.get('/admin/save-records', { params })
return data
}
export interface SaveRecord {
id: number
source_type: string
source_title: string | null
source_url: string
target_cloud: string
share_url: string | null
share_pwd: string | null
file_size: string | null
file_count: number
duration_ms: number
status: string
error_message: string | null
folder_name: string | null
folder_count: number
original_folder_name: string | null
ip_address: string | null
ip_location: string | null
created_at: string
}
// ===== 系统配置 =====
/** Save/update per-config notification settings */
export async function saveConfigNotify(
configId: number,
settings: Record<string, any>
): Promise<{ success: boolean; message: string }> {
const { data } = await api.put(`/admin/cloud-configs/${configId}/notify`, settings)
return data
}
/** Get per-config notification settings */
export async function getConfigNotify(
configId: number
): Promise<Record<string, any>> {
const { data } = await api.get(`/admin/cloud-configs/${configId}/notify`)
return data
}
/** Test a notification channel (global or per-config) */
export async function getAllNotifierProviders(): Promise<Record<string, { name: string; label: string; params: { key: string; label: string; type: string; required: boolean; placeholder?: string; default?: any }[] }>> {
const { data } = await api.get('/admin/notify/providers')
return data
}
export async function testNotifyChannel(
channelType: string,
configId?: number,
params?: Record<string, any>
): Promise<{ success: boolean; message: string }> {
const { data } = await api.post('/admin/notify/test', { channelType, configId, params })
return data
}
export async function getSystemConfigs(): Promise<{ key: string; value: string; description: string }[]> {
const { data } = await api.get('/admin/system-configs')
return data
}
export async function updateSystemConfigs(
entries: { key: string; value: string }[]
): Promise<void> {
await api.put('/admin/system-configs', { entries })
}
// ===== 网盘类型开关 =====
export async function getCloudTypes(): Promise<{ types: { type: string; label: string; icon: string; enabled: boolean }[] }> {
const { data } = await api.get('/admin/cloud-types')
return data
}
export async function toggleCloudType(type: string, enabled: boolean): Promise<void> {
await api.put('/admin/cloud-types', { type, enabled })
}
// ===== 修改密码 =====
export async function changePassword(
oldPassword: string,
newPassword: string
): Promise<{ success: boolean; message: string }> {
const { data } = await api.post('/admin/change-password', { oldPassword, newPassword })
return data
}
export default api
export { query as searchQuery }
// ===== 系统设置SettingsManage.vue =====
export async function getSettings(): Promise<any[]> {
const { data } = await api.get('/admin/system-configs')
return data
}
export async function updateSetting(key: string, value: string): Promise<void> {
await api.put('/admin/system-configs', { entries: [{ key, value }] })
}
export async function uploadFallbackImage(file: File): Promise<{ success: boolean; url: string; message: string }> {
const form = new FormData()
form.append('image', file)
const { data } = await api.post('/admin/upload-fallback-image', form, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return data
}
export async function uploadLogo(file: File): Promise<{ success: boolean; url: string; message: string }> {
const form = new FormData()
form.append('image', file)
const { data } = await api.post('/admin/upload-logo', form, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return data
}
export async function getSiteConfig(): Promise<{ site_logo: string; site_name: string; search_fallback_image: string; site_disclaimer: string }> {
const { data } = await api.get('/site-config')
return data
}
// ===== Redis 连接测试 =====
export async function testRedisConnection(url: string): Promise<{ ok: boolean; latency: number; info: string }> {
const { data } = await api.post('/admin/test-redis', { url })
return data
}
// ===== 外部服务连接测试 =====
export async function testExternalService(params: {
type: 'pansou' | 'video_parser' | 'tmdb' | 'proxy' | 'ip_geo'
url?: string
token?: string
}): Promise<{ ok: boolean; latency: number; info: string }> {
const { data } = await api.post('/admin/test-external-service', params)
return data
}
// ===== 数据库状态 =====
export async function getDbStatus(): Promise<{
db_size: string
db_path: string
save_records: number
search_stats: number
system_configs: number
cloud_configs: number
content_cache: number
redis_status: string
redis_url: string
}> {
const { data } = await api.get('/admin/db-status')
return data
}
// ===== 存储清理 =====
export async function runCleanup(): Promise<{
success: boolean
files_trashed: number
logs_deleted: number
trash_emptied: boolean
errors: string[]
message: string
}> {
const { data } = await api.post('/admin/cleanup/run')
return data
}
export async function emptyAllTrash(): Promise<{
success: boolean
emptied: boolean
errors: string[]
message: string
}> {
const { data } = await api.post('/admin/cleanup/empty-trash')
return data
}