feat: site management + cross-site torrent search

Port the complete site management system from the original MediaStation:

Model:
  - model/model.go: Added Site table with fields for name, base_url,
    site_type (nexusphp/gazelle/unit3d/mteam/custom_rss), auth_type
    (cookie/api_key/authorization), cookie, api_key, auth_header,
    user_agent, rss_url, timeout, priority, use_proxy, enabled,
    login_status, downloader. Registered in AllModels().

Backend services:
  - service/site.go: SiteService with CRUD (Create/List/FindByID/Update/
    Delete), TestConnection (validates credentials via HTTP, updates
    login_status in DB), and Search (cross-site fan-out that queries
    every enabled site adapter and returns merged results sorted by
    seeders descending).
  - service/site_adapter.go: SiteAdapter interface + NewSiteAdapter
    factory + 5 implementations:
    * nexusPhpAdapter — regex HTML scraping of torrents.php
    * gazelleAdapter — JSON API /ajax.php?action=browse
    * unit3dAdapter — REST API /api/torrents/filter
    * mteamAdapter — M-Team v3 POST /api/torrent/search with x-api-key
    * rssAdapter — XML RSS/Atom feed parsing with keyword filter

Handler + routes:
  - handler/sites.go: listSites, getSite, createSite, updateSite,
    deleteSite, testSite, siteSearch handlers.
  - handler/handler.go: 7 new routes under authed group:
    GET/POST /sites, GET/PUT/DELETE /sites/:id,
    POST /sites/:id/test, GET /sites/search.

Frontend:
  - api/sites.ts: typed interfaces (Site, SiteSearchResult,
    CreateSiteInput) and sitesAPI helper.
  - pages/SitesPage.tsx: full CRUD UI with add-site form (site_type +
    auth_type selectors, cookie/api_key/rss inputs), connection test
    button with live status indicator, delete action.
  - pages/SiteSearchPage.tsx: cross-site search with keyword input,
    merged result table (site/title/size/seeders/leechers/free),
    one-click download-to-qBittorrent button.
  - App.tsx: lazy-loaded routes /sites and /site-search.
  - Layout.tsx: sidebar links 站点管理 + 站点搜索 under 自动化 group.

Verified: go build + go vet + go test pass; tsc -b + vite build passes
(32 lazy chunks, main bundle 255 KB / 85 KB gzipped).
This commit is contained in:
Kiro
2026-05-16 08:29:00 +00:00
parent 778bf77437
commit 7a18eb670a
11 changed files with 1404 additions and 1 deletions
+77
View File
@@ -0,0 +1,77 @@
import { api } from './client'
export interface Site {
id: string
name: string
base_url: string
site_type: string
auth_type: string
cookie?: string
api_key?: string
auth_header?: string
user_agent?: string
rss_url?: string
timeout: number
priority: number
use_proxy: boolean
enabled: boolean
login_status: string
downloader?: string
created_at: string
updated_at: string
}
export interface SiteSearchResult {
site_name: string
site_id: string
title: string
torrent_url: string
download_url: string
size: number
seeders: number
leechers: number
free: boolean
}
export interface CreateSiteInput {
name: string
base_url: string
site_type?: string
auth_type?: string
cookie?: string
api_key?: string
auth_header?: string
user_agent?: string
rss_url?: string
timeout?: number
priority?: number
use_proxy?: boolean
enabled?: boolean
downloader?: string
}
export const sitesAPI = {
list: () => api.get<{ items: Site[] }>('/sites').then((r) => r.data.items),
get: (id: string) => api.get<Site>(`/sites/${id}`).then((r) => r.data),
create: (input: CreateSiteInput) =>
api.post<Site>('/sites', input).then((r) => r.data),
update: (id: string, patch: Partial<CreateSiteInput>) =>
api.put<Site>(`/sites/${id}`, patch).then((r) => r.data),
remove: (id: string) => api.delete(`/sites/${id}`).then((r) => r.data),
test: (id: string) =>
api
.post<{ success: boolean; message: string }>(`/sites/${id}/test`)
.then((r) => r.data),
search: (keyword: string) =>
api
.get<{ items: SiteSearchResult[]; total: number }>('/sites/search', {
params: { keyword },
})
.then((r) => r.data),
}