Compare commits
2 Commits
db5cdcba24
...
793627b046
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793627b046 | ||
|
|
4fea1f322a |
@@ -43,7 +43,7 @@ module.exports = {
|
|||||||
OLIST_FRONTEND_URL: process.env.OLIST_FRONTEND_URL || '',
|
OLIST_FRONTEND_URL: process.env.OLIST_FRONTEND_URL || '',
|
||||||
OLIST_TOKEN_ENCRYPTION_KEY: process.env.OLIST_TOKEN_ENCRYPTION_KEY || '',
|
OLIST_TOKEN_ENCRYPTION_KEY: process.env.OLIST_TOKEN_ENCRYPTION_KEY || '',
|
||||||
OLIST_SYNC_ENABLED: process.env.OLIST_SYNC_ENABLED === 'true',
|
OLIST_SYNC_ENABLED: process.env.OLIST_SYNC_ENABLED === 'true',
|
||||||
OLIST_SYNC_INTERVAL_MS: Number(process.env.OLIST_SYNC_INTERVAL_MS) || 24 * 60 * 60 * 1000,
|
OLIST_SYNC_HOUR: 10,
|
||||||
OLIST_SYNC_REQUEST_DELAY_MS: Math.max(2_500, Number(process.env.OLIST_SYNC_REQUEST_DELAY_MS) || 2_500),
|
OLIST_SYNC_REQUEST_DELAY_MS: Math.max(2_500, Number(process.env.OLIST_SYNC_REQUEST_DELAY_MS) || 2_500),
|
||||||
TURNSTILE_SITE_KEY,
|
TURNSTILE_SITE_KEY,
|
||||||
TURNSTILE_SECRET
|
TURNSTILE_SECRET
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const {
|
|||||||
OLIST_CLIENT_SECRET,
|
OLIST_CLIENT_SECRET,
|
||||||
OLIST_FRONTEND_URL,
|
OLIST_FRONTEND_URL,
|
||||||
OLIST_REDIRECT_URI,
|
OLIST_REDIRECT_URI,
|
||||||
OLIST_SYNC_INTERVAL_MS,
|
OLIST_SYNC_HOUR,
|
||||||
OLIST_SYNC_REQUEST_DELAY_MS,
|
OLIST_SYNC_REQUEST_DELAY_MS,
|
||||||
OLIST_TOKEN_ENCRYPTION_KEY
|
OLIST_TOKEN_ENCRYPTION_KEY
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
@@ -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,29 @@ const listSyncRuns = async (limit = 5) => {
|
|||||||
return result.rows.map(mapRun);
|
return result.rows.map(mapRun);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasScheduledSyncToday = async () => {
|
||||||
|
const result = await pool.query(`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM olist_sync_runs
|
||||||
|
WHERE trigger = 'scheduled'
|
||||||
|
AND started_at >= (date_trunc('day', CURRENT_TIMESTAMP AT TIME ZONE 'America/Sao_Paulo') AT TIME ZONE 'America/Sao_Paulo')
|
||||||
|
) AS exists;
|
||||||
|
`);
|
||||||
|
return Boolean(result.rows[0]?.exists);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTodayOlistRunAt = () => {
|
||||||
|
const parts = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: 'America/Sao_Paulo',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
}).formatToParts(new Date());
|
||||||
|
const value = (type) => parts.find(part => part.type === type)?.value;
|
||||||
|
return new Date(`${value('year')}-${value('month')}-${value('day')}T${String(OLIST_SYNC_HOUR).padStart(2, '0')}:00:00-03:00`);
|
||||||
|
};
|
||||||
|
|
||||||
const logRunEvent = async (runId, {
|
const logRunEvent = async (runId, {
|
||||||
level = 'info',
|
level = 'info',
|
||||||
eventType,
|
eventType,
|
||||||
@@ -235,6 +259,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 +716,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 scheduledRunAt = getTodayOlistRunAt();
|
||||||
}, OLIST_SYNC_INTERVAL_MS);
|
let delay = Math.max(0, scheduledRunAt.getTime() - Date.now());
|
||||||
return scheduledSync;
|
try {
|
||||||
|
if (delay === 0 && await hasScheduledSyncToday()) {
|
||||||
|
scheduledRunAt.setUTCDate(scheduledRunAt.getUTCDate() + 1);
|
||||||
|
delay = scheduledRunAt.getTime() - Date.now();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Could not calculate the next daily 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