154 lines
5.6 KiB
TypeScript
154 lines
5.6 KiB
TypeScript
import "dotenv/config";
|
|
import express from "express";
|
|
import { createServer } from "http";
|
|
import net from "net";
|
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
import * as cron from "node-cron";
|
|
import { registerOAuthRoutes } from "./oauth";
|
|
import { appRouter } from "../routers";
|
|
import { createContext } from "./context";
|
|
import { serveStatic, setupVite } from "./vite";
|
|
import { runFullImport } from "../importer";
|
|
import uploadRoutes from "../uploadRoutes";
|
|
import scheduledRoutes from "../scheduledRoutes";
|
|
import { ensureAdminExists } from "../localAuth";
|
|
import { getSetting } from "../db";
|
|
import { runRssFetch } from "../rssEngine";
|
|
|
|
function isPortAvailable(port: number): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const server = net.createServer();
|
|
server.listen(port, () => { server.close(() => resolve(true)); });
|
|
server.on("error", () => resolve(false));
|
|
});
|
|
}
|
|
|
|
async function findAvailablePort(startPort: number = 3000): Promise<number> {
|
|
for (let port = startPort; port < startPort + 20; port++) {
|
|
if (await isPortAvailable(port)) return port;
|
|
}
|
|
throw new Error(`No available port found starting from ${startPort}`);
|
|
}
|
|
|
|
// ─── Tâche d'import quotidien + lecture RSS ──────────────────────────────────
|
|
let cronJob: ReturnType<typeof cron.schedule> | null = null;
|
|
|
|
export async function scheduleDailyImport() {
|
|
const importTime = (await getSetting("import_time")) || "06:00";
|
|
const fetchMode = (await getSetting("fetch_mode")) || "daily";
|
|
const fetchIntervalMinutes = parseInt((await getSetting("fetch_interval_minutes")) || "1440", 10);
|
|
const fetchWeekDay = (await getSetting("fetch_week_day")) || "1"; // 0=dim, 1=lun, ..., 6=sam
|
|
const fetchMonthDay = (await getSetting("fetch_month_day")) || "1"; // 1-28
|
|
|
|
const [hour, minute] = importTime.split(":").map(Number);
|
|
const h = hour ?? 6;
|
|
const m = minute ?? 0;
|
|
|
|
let cronExpr: string;
|
|
let cronLabel: string;
|
|
|
|
if (fetchMode === "interval") {
|
|
const intervalMin = Math.max(60, fetchIntervalMinutes);
|
|
if (intervalMin % 60 === 0) {
|
|
const hours = intervalMin / 60;
|
|
cronExpr = hours === 24 ? `0 ${m} ${h} * * *` : `0 ${m} */${hours} * * *`;
|
|
} else {
|
|
cronExpr = `0 */${intervalMin} * * * *`;
|
|
}
|
|
const hours = intervalMin / 60;
|
|
cronLabel = `Mode intervalle — toutes les ${hours >= 1 ? hours + "h" : intervalMin + "min"} (${cronExpr})`;
|
|
} else if (fetchMode === "weekly") {
|
|
// hebdomadaire : le jour choisi à l'heure choisie
|
|
cronExpr = `0 ${m} ${h} * * ${fetchWeekDay}`;
|
|
const jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"];
|
|
cronLabel = `Mode hebdomadaire — chaque ${jours[parseInt(fetchWeekDay)] ?? "lundi"} à ${importTime} (${cronExpr})`;
|
|
} else if (fetchMode === "monthly") {
|
|
// mensuel : le jour du mois choisi à l'heure choisie
|
|
const day = Math.min(28, Math.max(1, parseInt(fetchMonthDay)));
|
|
cronExpr = `0 ${m} ${h} ${day} * *`;
|
|
cronLabel = `Mode mensuel — le ${day} de chaque mois à ${importTime} (${cronExpr})`;
|
|
} else {
|
|
// daily (défaut)
|
|
cronExpr = `0 ${m} ${h} * * *`;
|
|
cronLabel = `Mode quotidien — tous les jours à ${importTime} (${cronExpr})`;
|
|
}
|
|
|
|
console.log(`[Cron] ${cronLabel}`);
|
|
|
|
if (cronJob) {
|
|
cronJob.stop();
|
|
cronJob = null;
|
|
}
|
|
cronJob = cron.schedule(cronExpr, async () => {
|
|
console.log(`[Cron] Import automatique démarré à ${new Date().toISOString()}`);
|
|
try {
|
|
// 1. Import des fichiers Excel (Veille + AAP)
|
|
const result = await runFullImport();
|
|
console.log(`[Cron] Import Excel terminé — Veille: +${result.veille.newRows} | AAP: +${result.aap.newRows}`);
|
|
} catch (e) {
|
|
console.error("[Cron] Erreur lors de l'import Excel:", e);
|
|
}
|
|
try {
|
|
// 2. Lecture des flux RSS
|
|
const rssSummary = await runRssFetch();
|
|
console.log(
|
|
`[Cron] Lecture RSS terminée — ${rssSummary.totalFeeds} flux, ` +
|
|
`+${rssSummary.totalNewItems} nouveaux articles, ` +
|
|
`${rssSummary.errorFeeds} erreur(s)`
|
|
);
|
|
} catch (e) {
|
|
console.error("[Cron] Erreur lors de la lecture RSS:", e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stub conservé pour compatibilité avec les imports existants dans routers.ts.
|
|
* La lecture RSS est désormais intégrée dans scheduleDailyImport().
|
|
*/
|
|
export async function scheduleRssFetch() {
|
|
// No-op : la lecture RSS est pilotée par le cron d'import quotidien (import_time)
|
|
console.log("[RSS Cron] Planificateur RSS indépendant désactivé — la lecture est pilotée par le cron d'import quotidien.");
|
|
}
|
|
|
|
async function startServer() {
|
|
const app = express();
|
|
const server = createServer(app);
|
|
|
|
app.use(express.json({ limit: "50mb" }));
|
|
app.use(express.urlencoded({ limit: "50mb", extended: true }));
|
|
|
|
registerOAuthRoutes(app);
|
|
app.use(uploadRoutes);
|
|
app.use(scheduledRoutes);
|
|
app.use(
|
|
"/api/trpc",
|
|
createExpressMiddleware({ router: appRouter, createContext })
|
|
);
|
|
|
|
if (process.env.NODE_ENV === "development") {
|
|
await setupVite(app, server);
|
|
} else {
|
|
serveStatic(app);
|
|
}
|
|
|
|
const preferredPort = parseInt(process.env.PORT || "3000");
|
|
const port = await findAvailablePort(preferredPort);
|
|
if (port !== preferredPort) {
|
|
console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
|
|
}
|
|
|
|
server.listen(port, async () => {
|
|
console.log(`Server running on http://localhost:${port}/`);
|
|
// Initialisation post-démarrage
|
|
try {
|
|
await ensureAdminExists();
|
|
await scheduleDailyImport();
|
|
} catch (e) {
|
|
console.error("[Init] Erreur d'initialisation:", e);
|
|
}
|
|
});
|
|
}
|
|
|
|
startServer().catch(console.error);
|