feat: intégration planificateur RSS natif (cron interne Node.js)

- 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
This commit is contained in:
Manus Deploy
2026-05-02 19:43:38 +02:00
parent 76a71ebc2c
commit 91a0b21c52
2 changed files with 68 additions and 8 deletions

View File

@@ -12,7 +12,8 @@ import { runFullImport } from "../importer";
import uploadRoutes from "../uploadRoutes"; import uploadRoutes from "../uploadRoutes";
import scheduledRoutes from "../scheduledRoutes"; import scheduledRoutes from "../scheduledRoutes";
import { ensureAdminExists } from "../localAuth"; import { ensureAdminExists } from "../localAuth";
import { getSetting } from "../db"; import { getSetting, getRssSettings } from "../db";
import { runRssFetch } from "../rssEngine";
function isPortAvailable(port: number): Promise<boolean> { function isPortAvailable(port: number): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
@@ -30,7 +31,6 @@ async function findAvailablePort(startPort: number = 3000): Promise<number> {
} }
// ─── Tâche d'import quotidien ───────────────────────────────────────────────── // ─── Tâche d'import quotidien ─────────────────────────────────────────────────
let cronJob: ReturnType<typeof cron.schedule> | null = null; let cronJob: ReturnType<typeof cron.schedule> | null = null;
async function scheduleDailyImport() { async function scheduleDailyImport() {
@@ -38,12 +38,10 @@ async function scheduleDailyImport() {
const importTime = (await getSetting("import_time")) || "06:00"; const importTime = (await getSetting("import_time")) || "06:00";
const [hour, minute] = importTime.split(":").map(Number); const [hour, minute] = importTime.split(":").map(Number);
const cronExpr = `0 ${minute ?? 0} ${hour ?? 6} * * *`; const cronExpr = `0 ${minute ?? 0} ${hour ?? 6} * * *`;
if (cronJob) { if (cronJob) {
cronJob.stop(); cronJob.stop();
cronJob = null; cronJob = null;
} }
cronJob = cron.schedule(cronExpr, async () => { cronJob = cron.schedule(cronExpr, async () => {
console.log(`[Cron] Import automatique démarré à ${new Date().toISOString()}`); console.log(`[Cron] Import automatique démarré à ${new Date().toISOString()}`);
try { try {
@@ -53,10 +51,71 @@ async function scheduleDailyImport() {
console.error("[Cron] Erreur lors de l'import:", e); console.error("[Cron] Erreur lors de l'import:", e);
} }
}); });
console.log(`[Cron] Import quotidien planifié à ${importTime} (${cronExpr})`); console.log(`[Cron] Import quotidien planifié à ${importTime} (${cronExpr})`);
} }
// ─── Planificateur RSS natif ──────────────────────────────────────────────────
let rssCronJob: ReturnType<typeof cron.schedule> | 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() { async function startServer() {
const app = express(); const app = express();
const server = createServer(app); const server = createServer(app);
@@ -67,7 +126,6 @@ async function startServer() {
registerOAuthRoutes(app); registerOAuthRoutes(app);
app.use(uploadRoutes); app.use(uploadRoutes);
app.use(scheduledRoutes); app.use(scheduledRoutes);
app.use( app.use(
"/api/trpc", "/api/trpc",
createExpressMiddleware({ router: appRouter, createContext }) createExpressMiddleware({ router: appRouter, createContext })
@@ -81,18 +139,17 @@ async function startServer() {
const preferredPort = parseInt(process.env.PORT || "3000"); const preferredPort = parseInt(process.env.PORT || "3000");
const port = await findAvailablePort(preferredPort); const port = await findAvailablePort(preferredPort);
if (port !== preferredPort) { if (port !== preferredPort) {
console.log(`Port ${preferredPort} is busy, using port ${port} instead`); console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
} }
server.listen(port, async () => { server.listen(port, async () => {
console.log(`Server running on http://localhost:${port}/`); console.log(`Server running on http://localhost:${port}/`);
// Initialisation post-démarrage // Initialisation post-démarrage
try { try {
await ensureAdminExists(); await ensureAdminExists();
await scheduleDailyImport(); await scheduleDailyImport();
await scheduleRssFetch();
} catch (e) { } catch (e) {
console.error("[Init] Erreur d'initialisation:", e); console.error("[Init] Erreur d'initialisation:", e);
} }

View File

@@ -33,6 +33,7 @@ import {
saveRssSettings, saveRssSettings,
} from "./db"; } from "./db";
import { importVeille, importAAP, runFullImport, getImportConfig } from "./importer"; import { importVeille, importAAP, runFullImport, getImportConfig } from "./importer";
import { scheduleRssFetch } from "./_core/index";
import { loginLocalUser, hashPassword, ensureAdminExists } from "./localAuth"; import { loginLocalUser, hashPassword, ensureAdminExists } from "./localAuth";
// ─── Middleware admin ───────────────────────────────────────────────────────── // ─── Middleware admin ─────────────────────────────────────────────────────────
@@ -408,6 +409,8 @@ export const appRouter = router({
})) }))
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
await saveRssSettings(input); await saveRssSettings(input);
// Recharger le planificateur RSS avec les nouveaux paramètres
await scheduleRssFetch();
return { success: true }; return { success: true };
}), }),
}), }),