From 91a0b21c52d9fb220577d122eb82178ff0e6e1b7 Mon Sep 17 00:00:00 2001 From: Manus Deploy Date: Sat, 2 May 2026 19:43:38 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20int=C3=A9gration=20planificateur=20RSS?= =?UTF-8?q?=20natif=20(cron=20interne=20Node.js)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout de scheduleRssFetch() dans server/_core/index.ts - Planificateur démarré au lancement du serveur - Supporte les modes interval et scheduled depuis rss_settings - Rechargement dynamique lors de la sauvegarde des paramètres RSS - Supprime la dépendance à la tâche planifiée Manus externe --- server/_core/index.ts | 73 ++++++++++++++++++++++++++++++++++++++----- server/routers.ts | 3 ++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/server/_core/index.ts b/server/_core/index.ts index e784363..24f6b80 100644 --- a/server/_core/index.ts +++ b/server/_core/index.ts @@ -12,7 +12,8 @@ import { runFullImport } from "../importer"; import uploadRoutes from "../uploadRoutes"; import scheduledRoutes from "../scheduledRoutes"; import { ensureAdminExists } from "../localAuth"; -import { getSetting } from "../db"; +import { getSetting, getRssSettings } from "../db"; +import { runRssFetch } from "../rssEngine"; function isPortAvailable(port: number): Promise { return new Promise(resolve => { @@ -30,7 +31,6 @@ async function findAvailablePort(startPort: number = 3000): Promise { } // ─── Tâche d'import quotidien ───────────────────────────────────────────────── - let cronJob: ReturnType | null = null; async function scheduleDailyImport() { @@ -38,12 +38,10 @@ async function scheduleDailyImport() { const importTime = (await getSetting("import_time")) || "06:00"; const [hour, minute] = importTime.split(":").map(Number); const cronExpr = `0 ${minute ?? 0} ${hour ?? 6} * * *`; - if (cronJob) { cronJob.stop(); cronJob = null; } - cronJob = cron.schedule(cronExpr, async () => { console.log(`[Cron] Import automatique démarré à ${new Date().toISOString()}`); try { @@ -53,10 +51,71 @@ async function scheduleDailyImport() { console.error("[Cron] Erreur lors de l'import:", e); } }); - console.log(`[Cron] Import quotidien planifié à ${importTime} (${cronExpr})`); } +// ─── Planificateur RSS natif ────────────────────────────────────────────────── +let rssCronJob: ReturnType | null = null; + +/** + * Démarre (ou redémarre) le planificateur RSS en lisant la configuration + * depuis la table rss_settings. Peut être appelé au démarrage et à chaque + * modification des paramètres RSS via l'interface d'administration. + */ +export async function scheduleRssFetch() { + // Arrêter le cron existant s'il y en a un + if (rssCronJob) { + rssCronJob.stop(); + rssCronJob = null; + console.log("[RSS Cron] Planificateur RSS arrêté."); + } + + const settings = await getRssSettings(); + + if (!settings || !settings.autoFetchEnabled) { + console.log("[RSS Cron] Lecture automatique des flux RSS désactivée."); + return; + } + + let cronExpr: string; + + if (settings.fetchMode === "interval") { + // Mode intervalle : toutes les N minutes + const intervalMin = Math.max(5, settings.fetchIntervalMinutes ?? 60); + if (intervalMin < 60) { + cronExpr = `*/${intervalMin} * * * *`; + } else if (intervalMin % 60 === 0) { + const hours = intervalMin / 60; + cronExpr = `0 */${hours} * * *`; + } else { + cronExpr = `*/${intervalMin} * * * *`; + } + console.log(`[RSS Cron] Mode intervalle — toutes les ${intervalMin} minutes (${cronExpr})`); + } else { + // Mode planifié : heure fixe quotidienne + const scheduledTime = settings.scheduledTime ?? "06:00"; + const [hour, minute] = scheduledTime.split(":").map(Number); + cronExpr = `0 ${minute ?? 0} ${hour ?? 6} * * *`; + console.log(`[RSS Cron] Mode planifié — tous les jours à ${scheduledTime} (${cronExpr})`); + } + + rssCronJob = cron.schedule(cronExpr, async () => { + console.log(`[RSS Cron] Lecture des flux RSS démarrée à ${new Date().toISOString()}`); + try { + const summary = await runRssFetch(); + console.log( + `[RSS Cron] Lecture terminée — ${summary.totalFeeds} flux, ` + + `+${summary.totalNewItems} nouveaux articles, ` + + `${summary.errorFeeds} erreur(s)` + ); + } catch (e) { + console.error("[RSS Cron] Erreur lors de la lecture des flux:", e); + } + }); + + console.log("[RSS Cron] Planificateur RSS démarré."); +} + async function startServer() { const app = express(); const server = createServer(app); @@ -67,7 +126,6 @@ async function startServer() { registerOAuthRoutes(app); app.use(uploadRoutes); app.use(scheduledRoutes); - app.use( "/api/trpc", createExpressMiddleware({ router: appRouter, createContext }) @@ -81,18 +139,17 @@ async function startServer() { 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(); + await scheduleRssFetch(); } catch (e) { console.error("[Init] Erreur d'initialisation:", e); } diff --git a/server/routers.ts b/server/routers.ts index a2dd539..91058f5 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -33,6 +33,7 @@ import { saveRssSettings, } from "./db"; import { importVeille, importAAP, runFullImport, getImportConfig } from "./importer"; +import { scheduleRssFetch } from "./_core/index"; import { loginLocalUser, hashPassword, ensureAdminExists } from "./localAuth"; // ─── Middleware admin ───────────────────────────────────────────────────────── @@ -408,6 +409,8 @@ export const appRouter = router({ })) .mutation(async ({ input }) => { await saveRssSettings(input); + // Recharger le planificateur RSS avec les nouveaux paramètres + await scheduleRssFetch(); return { success: true }; }), }),