feat: DSL 磁力聚合 — 搜索/解析/预览/投递 + cilisousuo 源 + 中英双语 + Docker 部署

This commit is contained in:
ice-a
2026-10-16 18:53:45 +08:00
commit d6999104bf
64 changed files with 16524 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
import { MongoClient, type Collection, type Db } from 'mongodb'
import type { DownloadDoc, SearchDoc, TorrentDoc } from './types'
let client: MongoClient | null = null
let connecting: Promise<MongoClient> | null = null
let indexState: 'pending' | 'done' = 'pending'
export const mongoUri = () => process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017'
export const mongoDbName = () => process.env.MONGODB_DB || 'torrent_console'
/** 最近一次连接失败信息,用于在 UI 上提示用户去装 / 启 MongoDB */
let lastError: { message: string; at: Date } | null = null
export async function getClient(): Promise<MongoClient> {
if (client) return client
if (!connecting) {
const next = new MongoClient(mongoUri(), {
serverSelectionTimeoutMS: 3000,
connectTimeoutMS: 3000
})
connecting = next
.connect()
.then((cli) => {
client = cli
connecting = null
lastError = null
return cli
})
.catch((err: unknown) => {
connecting = null
lastError = { message: err instanceof Error ? err.message : String(err), at: new Date() }
next.close().catch(() => {})
throw err
})
}
return connecting
}
export async function getDb(): Promise<Db> {
const cli = await getClient()
const db = cli.db(mongoDbName())
if (indexState === 'pending') {
await ensureIndexes(db)
indexState = 'done'
}
return db
}
async function ensureIndexes(db: Db) {
await db.collection<TorrentDoc>('torrents').createIndex({ infoHash: 1 }, { unique: true })
await db.collection<TorrentDoc>('torrents').createIndex({ updatedAt: -1 })
await db.collection<SearchDoc>('searches').createIndex({ createdAt: -1 })
await db.collection<SearchDoc>('searches').createIndex({ keyword: 1 })
await db.collection<DownloadDoc>('downloads').createIndex({ createdAt: -1 })
}
export async function torrentsCol(): Promise<Collection<TorrentDoc>> {
return (await getDb()).collection<TorrentDoc>('torrents')
}
export async function searchesCol(): Promise<Collection<SearchDoc>> {
return (await getDb()).collection<SearchDoc>('searches')
}
export async function downloadsCol(): Promise<Collection<DownloadDoc>> {
return (await getDb()).collection<DownloadDoc>('downloads')
}
/** 供 /api/health 使用:探测 MongoDB 是否可用 */
export async function mongoStatus() {
try {
const db = await getDb()
await db.command({ ping: 1 })
return { ok: true, uri: mongoUri(), db: mongoDbName(), error: null as string | null }
} catch (err) {
return {
ok: false,
uri: mongoUri(),
db: mongoDbName(),
error: lastError?.message || (err instanceof Error ? err.message : String(err))
}
}
}
/** MongoDB 不可用时的兜底:只记录日志,不打断主流程 */
export async function tryMongo<T>(fn: () => Promise<T>): Promise<T | null> {
try {
return await fn()
} catch (err) {
console.warn('[mongo] 操作失败,已跳过:', err instanceof Error ? err.message : err)
return null
}
}