From 4fea1f322a7e3a06266f0a99c33b7babcef1f7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 12 Aug 2026 09:12:14 -0300 Subject: [PATCH] Schedule Olist sync from last completion --- backend/index.js | 2 +- backend/services/olistService.js | 51 ++++++++++++++++++++++++++++---- src/pages/OlistSync.tsx | 2 +- src/types.ts | 1 + 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/backend/index.js b/backend/index.js index 6164722..b8731ea 100644 --- a/backend/index.js +++ b/backend/index.js @@ -8,7 +8,7 @@ const start = async () => { await markInterruptedOlistRuns(); const app = createApp(); - if (OLIST_SYNC_ENABLED) scheduleOlistSync(); + if (OLIST_SYNC_ENABLED) await scheduleOlistSync(); app.listen(PORT, '0.0.0.0', () => { console.log(`Nexstar Backend running at http://localhost:${PORT}`); console.log(`Endpoint for n8n: POST http://localhost:${PORT}/api/data`); diff --git a/backend/services/olistService.js b/backend/services/olistService.js index 6482793..424e382 100644 --- a/backend/services/olistService.js +++ b/backend/services/olistService.js @@ -20,6 +20,7 @@ let activeSync = null; let activeRunId = null; let stopRequested = false; let scheduledSync = null; +let nextScheduledSyncAt = null; let nextOlistRequestAt = 0; const normalizeText = (value) => String(value || '').trim(); @@ -110,6 +111,17 @@ const listSyncRuns = async (limit = 5) => { return result.rows.map(mapRun); }; +const getLastCompletedSyncAt = async () => { + const result = await pool.query(` + SELECT completed_at + FROM olist_sync_runs + WHERE status = 'completed' AND completed_at IS NOT NULL + ORDER BY completed_at DESC + LIMIT 1; + `); + return result.rows[0]?.completed_at || null; +}; + const logRunEvent = async (runId, { level = 'info', eventType, @@ -235,6 +247,7 @@ const getOlistStatus = async () => { connected: Boolean(connection), tokenExpiresAt: connection?.token_expires_at || null, syncInProgress: Boolean(activeSync), + nextScheduledSyncAt, productCache, runs }; @@ -691,12 +704,38 @@ const requestOlistSyncStop = async () => { return { accepted: true, initializing: false }; }; -const scheduleOlistSync = () => { - if (scheduledSync) clearInterval(scheduledSync); - scheduledSync = setInterval(() => { - startOlistSync({ trigger: 'scheduled' }).catch(error => console.error('Scheduled Olist sync skipped:', error.message)); - }, OLIST_SYNC_INTERVAL_MS); - return scheduledSync; +const scheduleOlistSync = async () => { + if (scheduledSync) clearTimeout(scheduledSync); + + let delay = OLIST_SYNC_INTERVAL_MS; + try { + const lastCompletedAt = await getLastCompletedSyncAt(); + const lastCompletedMs = lastCompletedAt ? new Date(lastCompletedAt).getTime() : Number.NaN; + if (!Number.isNaN(lastCompletedMs)) { + delay = Math.max(0, lastCompletedMs + OLIST_SYNC_INTERVAL_MS - Date.now()); + } + } catch (error) { + console.error('Could not calculate the next Olist sync:', error.message); + } + + nextScheduledSyncAt = new Date(Date.now() + delay).toISOString(); + scheduledSync = setTimeout(async () => { + scheduledSync = null; + nextScheduledSyncAt = null; + try { + assertConfigured(); + if (!await getConnection()) { + console.error('Scheduled Olist sync skipped: no connected Olist account.'); + return; + } + await runOlistSync({ trigger: 'scheduled' }); + } catch (error) { + console.error('Scheduled Olist sync failed:', error.message); + } finally { + await scheduleOlistSync(); + } + }, delay); + return nextScheduledSyncAt; }; const getOlistFrontendRedirect = (outcome) => `${OLIST_FRONTEND_URL.replace(/\/$/, '')}/#/admin/olist?olist=${outcome}`; diff --git a/src/pages/OlistSync.tsx b/src/pages/OlistSync.tsx index 23755a6..1f6867b 100644 --- a/src/pages/OlistSync.tsx +++ b/src/pages/OlistSync.tsx @@ -184,7 +184,7 @@ const OlistSync = () => {
Execução atual

{latestRun?.status === 'running' ? (latestRun.cancelRequestedAt ? 'Parando' : 'Em andamento') : 'Em espera'}

-

Duração: {formatDuration(latestRun?.startedAt || null, latestRun?.status === 'running' ? null : latestRun?.completedAt || null, now)}

+

{latestRun?.status === 'running' ? `Duração: ${formatDuration(latestRun.startedAt, null, now)}` : `Próxima automática: ${formatDateTime(status?.nextScheduledSyncAt || null)}`}

Estruturas
diff --git a/src/types.ts b/src/types.ts index f3eab74..efe7109 100644 --- a/src/types.ts +++ b/src/types.ts @@ -246,6 +246,7 @@ export interface OlistStatus { connected: boolean; tokenExpiresAt: string | null; syncInProgress: boolean; + nextScheduledSyncAt: string | null; productCache: { total: number; checked: number;