Schedule Olist sync from last completion
This commit is contained in:
@@ -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`);
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user