import { MongoClient, type Collection, type Db } from 'mongodb' import type { DownloadDoc, SearchDoc, TorrentDoc } from './types' let client: MongoClient | null = null let connecting: Promise | 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 { if (client) return client if (!connecting) { const next = new MongoClient(mongoUri(), { serverSelectionTimeoutMS: 10000, connectTimeoutMS: 20000, socketTimeoutMS: 30000, retryWrites: true }) 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 { 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('torrents').createIndex({ infoHash: 1 }, { unique: true }) await db.collection('torrents').createIndex({ updatedAt: -1 }) await db.collection('searches').createIndex({ createdAt: -1 }) await db.collection('searches').createIndex({ keyword: 1 }) await db.collection('downloads').createIndex({ createdAt: -1 }) } export async function torrentsCol(): Promise> { return (await getDb()).collection('torrents') } export async function searchesCol(): Promise> { return (await getDb()).collection('searches') } export async function downloadsCol(): Promise> { return (await getDb()).collection('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(fn: () => Promise): Promise { try { return await fn() } catch (err) { console.warn('[mongo] 操作失败,已跳过:', err instanceof Error ? err.message : err) return null } }