Files
q-buffer/apps/server/src/index.ts
2026-01-02 15:49:01 +03:00

113 lines
3.6 KiB
TypeScript

import express from "express";
import http from "node:http";
import path from "node:path";
import cookieParser from "cookie-parser";
import cors from "cors";
import { config, isDev } from "./config"
import { ensureDataPaths } from "./storage/paths"
import { initAuth } from "./auth/auth.service"
import authRoutes from "./auth/auth.routes"
import { requireAuth } from "./auth/auth.middleware"
import qbitRoutes from "./qbit/qbit.routes"
import torrentRoutes from "./torrent/torrent.routes"
import loopRoutes from "./loop/loop.routes"
import profilesRoutes from "./loop/profiles.routes"
import statusRoutes from "./status/status.routes"
import timerRoutes from "./timer/timer.routes"
import { QbitClient } from "./qbit/qbit.client"
import { detectCapabilities } from "./qbit/qbit.capabilities"
import { setQbitClient, setQbitCapabilities } from "./qbit/qbit.context"
import { setQbitStatus } from "./status/status.service"
import { createSocketServer } from "./realtime/socket"
import { initEmitter, emitQbitHealth } from "./realtime/emitter"
import { startLoopScheduler } from "./loop/loop.scheduler"
import { startEnforcementWorker } from "./enforcement/enforcement.worker"
import { startTimerWorker } from "./timer/timer.worker"
import { logger } from "./utils/logger"
process.on("unhandledRejection", (reason) => {
logger.error({ reason }, "Unhandled promise rejection");
});
process.on("uncaughtException", (error) => {
logger.error({ error }, "Uncaught exception");
});
let serverStarted = false;
const bootstrap = async () => {
await ensureDataPaths();
await initAuth();
const app = express();
app.use(cookieParser());
app.use(express.json());
if (isDev) {
const fallbackOrigin = `http://localhost:${config.webPort}`;
const origins = [config.webOrigin || fallbackOrigin, fallbackOrigin];
app.use(
cors({
origin: origins,
credentials: true,
})
);
}
app.use("/api/auth", authRoutes);
app.use("/api/qbit", requireAuth, qbitRoutes);
app.use("/api/torrent", requireAuth, torrentRoutes);
app.use("/api/loop", requireAuth, loopRoutes);
app.use("/api/profiles", requireAuth, profilesRoutes);
app.use("/api/status", requireAuth, statusRoutes);
app.use("/api/timer", requireAuth, timerRoutes);
if (!isDev) {
app.use(express.static(config.webPublicDir));
app.get("*", (req, res, next) => {
if (req.path.startsWith("/api")) {
return next();
}
return res.sendFile(path.join(config.webPublicDir, "index.html"));
});
}
const server = http.createServer(app);
const io = createSocketServer(server);
initEmitter(io);
const qbit = new QbitClient();
setQbitClient(qbit);
try {
const caps = await detectCapabilities(qbit);
setQbitCapabilities(caps);
setQbitStatus({ ok: true, version: caps.version, capabilities: caps });
emitQbitHealth({ ok: true, version: caps.version, capabilities: caps });
} catch (error) {
logger.error({ error }, "Failed to connect to qBittorrent");
setQbitStatus({ ok: false, lastError: (error as Error).message });
emitQbitHealth({ ok: false, lastError: (error as Error).message });
}
startLoopScheduler(qbit, config.pollIntervalMs);
startEnforcementWorker(config.enforceIntervalMs);
startTimerWorker(qbit, config.timerPollMs);
server.listen(config.port, () => {
serverStarted = true;
logger.info(`q-buffer server listening on ${config.port}`);
});
};
const startWithRetry = () => {
bootstrap().catch((error) => {
logger.error({ error }, "Failed to start server");
if (!serverStarted) {
setTimeout(startWithRetry, 5000);
}
});
};
startWithRetry();