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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user