113 lines
3.6 KiB
TypeScript
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();
|