Files
hihala-dashboard/server/src/routes/etl.ts
T
fahed 9064df82be
Deploy HiHala Dashboard / deploy (push) Successful in 8s
feat: fire-and-forget ETL sync with progress status endpoint
POST /api/etl/sync now returns immediately (202-style).
GET /api/etl/status shows running state, current month being
processed, and final result or error when done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 13:40:19 +03:00

59 lines
2.0 KiB
TypeScript

import { Router, Request, Response } from 'express';
import { etl } from '../config';
import { runSync, SyncResult } from '../services/etlSync';
const router = Router();
type SyncState =
| { status: 'idle' }
| { status: 'running'; mode: string; startedAt: string; currentMonth?: string }
| { status: 'done'; result: SyncResult; finishedAt: string }
| { status: 'error'; error: string; finishedAt: string };
let syncState: SyncState = { status: 'idle' };
function auth(req: Request, res: Response): boolean {
const header = req.headers.authorization;
if (etl.secret && header !== `Bearer ${etl.secret}`) {
res.status(401).json({ error: 'Unauthorized' });
return false;
}
return true;
}
// POST /api/etl/sync?mode=full|incremental — fires and returns immediately
router.post('/sync', (req: Request, res: Response) => {
if (!auth(req, res)) return;
if (syncState.status === 'running') {
res.status(409).json({ error: 'Sync already running', state: syncState });
return;
}
const mode = (req.query.mode as string) === 'full' ? 'full' : 'incremental';
syncState = { status: 'running', mode, startedAt: new Date().toISOString() };
res.json({ accepted: true, mode, message: 'Sync started — poll GET /api/etl/status for progress' });
console.log(`\nETL sync started (${mode})...`);
runSync(mode, (month) => {
if (syncState.status === 'running') syncState = { ...syncState, currentMonth: month };
})
.then(result => {
console.log(`ETL sync complete: ${result.transactionsFetched} transactions → ${result.recordsWritten} records in ${result.duration}`);
syncState = { status: 'done', result, finishedAt: new Date().toISOString() };
})
.catch(err => {
console.error('ETL sync failed:', (err as Error).message);
syncState = { status: 'error', error: (err as Error).message, finishedAt: new Date().toISOString() };
});
});
// GET /api/etl/status
router.get('/status', (req: Request, res: Response) => {
if (!auth(req, res)) return;
res.json(syncState);
});
export default router;