Schedule Olist sync from last completion

This commit is contained in:
Cauê Faleiros
2026-08-12 09:12:14 -03:00
parent db5cdcba24
commit 4fea1f322a
4 changed files with 48 additions and 8 deletions

View File

@@ -8,7 +8,7 @@ const start = async () => {
await markInterruptedOlistRuns(); await markInterruptedOlistRuns();
const app = createApp(); const app = createApp();
if (OLIST_SYNC_ENABLED) scheduleOlistSync(); if (OLIST_SYNC_ENABLED) await scheduleOlistSync();
app.listen(PORT, '0.0.0.0', () => { app.listen(PORT, '0.0.0.0', () => {
console.log(`Nexstar Backend running at http://localhost:${PORT}`); console.log(`Nexstar Backend running at http://localhost:${PORT}`);
console.log(`Endpoint for n8n: POST http://localhost:${PORT}/api/data`); console.log(`Endpoint for n8n: POST http://localhost:${PORT}/api/data`);

View File

@@ -20,6 +20,7 @@ let activeSync = null;
let activeRunId = null; let activeRunId = null;
let stopRequested = false; let stopRequested = false;
let scheduledSync = null; let scheduledSync = null;
let nextScheduledSyncAt = null;
let nextOlistRequestAt = 0; let nextOlistRequestAt = 0;
const normalizeText = (value) => String(value || '').trim(); const normalizeText = (value) => String(value || '').trim();
@@ -110,6 +111,17 @@ const listSyncRuns = async (limit = 5) => {
return result.rows.map(mapRun); 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, { const logRunEvent = async (runId, {
level = 'info', level = 'info',
eventType, eventType,
@@ -235,6 +247,7 @@ const getOlistStatus = async () => {
connected: Boolean(connection), connected: Boolean(connection),
tokenExpiresAt: connection?.token_expires_at || null, tokenExpiresAt: connection?.token_expires_at || null,
syncInProgress: Boolean(activeSync), syncInProgress: Boolean(activeSync),
nextScheduledSyncAt,
productCache, productCache,
runs runs
}; };
@@ -691,12 +704,38 @@ const requestOlistSyncStop = async () => {
return { accepted: true, initializing: false }; return { accepted: true, initializing: false };
}; };
const scheduleOlistSync = () => { const scheduleOlistSync = async () => {
if (scheduledSync) clearInterval(scheduledSync); if (scheduledSync) clearTimeout(scheduledSync);
scheduledSync = setInterval(() => {
startOlistSync({ trigger: 'scheduled' }).catch(error => console.error('Scheduled Olist sync skipped:', error.message)); let delay = OLIST_SYNC_INTERVAL_MS;
}, OLIST_SYNC_INTERVAL_MS); try {
return scheduledSync; 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}`; const getOlistFrontendRedirect = (outcome) => `${OLIST_FRONTEND_URL.replace(/\/$/, '')}/#/admin/olist?olist=${outcome}`;

View File

@@ -184,7 +184,7 @@ const OlistSync = () => {
<div className="rounded-2xl border border-dark-border bg-dark-card p-5"> <div className="rounded-2xl border border-dark-border bg-dark-card p-5">
<div className="flex items-center justify-between"><span className="text-xs font-bold uppercase tracking-widest text-dark-muted">Execução atual</span><Timer className="h-4 w-4 text-brand-primary" /></div> <div className="flex items-center justify-between"><span className="text-xs font-bold uppercase tracking-widest text-dark-muted">Execução atual</span><Timer className="h-4 w-4 text-brand-primary" /></div>
<p className="mt-4 text-lg font-bold text-dark-text">{latestRun?.status === 'running' ? (latestRun.cancelRequestedAt ? 'Parando' : 'Em andamento') : 'Em espera'}</p> <p className="mt-4 text-lg font-bold text-dark-text">{latestRun?.status === 'running' ? (latestRun.cancelRequestedAt ? 'Parando' : 'Em andamento') : 'Em espera'}</p>
<p className="mt-1 text-xs font-semibold text-dark-muted">Duração: {formatDuration(latestRun?.startedAt || null, latestRun?.status === 'running' ? null : latestRun?.completedAt || null, now)}</p> <p className="mt-1 text-xs font-semibold text-dark-muted">{latestRun?.status === 'running' ? `Duração: ${formatDuration(latestRun.startedAt, null, now)}` : `Próxima automática: ${formatDateTime(status?.nextScheduledSyncAt || null)}`}</p>
</div> </div>
<div className="rounded-2xl border border-dark-border bg-dark-card p-5"> <div className="rounded-2xl border border-dark-border bg-dark-card p-5">
<div className="flex items-center justify-between"><span className="text-xs font-bold uppercase tracking-widest text-dark-muted">Estruturas</span><Database className="h-4 w-4 text-brand-primary" /></div> <div className="flex items-center justify-between"><span className="text-xs font-bold uppercase tracking-widest text-dark-muted">Estruturas</span><Database className="h-4 w-4 text-brand-primary" /></div>

View File

@@ -246,6 +246,7 @@ export interface OlistStatus {
connected: boolean; connected: boolean;
tokenExpiresAt: string | null; tokenExpiresAt: string | null;
syncInProgress: boolean; syncInProgress: boolean;
nextScheduledSyncAt: string | null;
productCache: { productCache: {
total: number; total: number;
checked: number; checked: number;