feat: DSL 磁力聚合 — 搜索/解析/预览/投递 + cilisousuo 源 + 中英双语 + Docker 部署
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user