- server/finance/ (19 файлов) удалён: пул финансовой БД перенесён в server/finance-di2/db-core.ts, db-client.ts теперь реэкспортирует из ./db-core - Шедулер аудита перенесён в server/finance-di2/audit-scheduler.ts (03:00 МСК, setInterval, без node-cron), запуск из registerDi2Routes с защитой от повторного старта - История аудита DI2 теперь персистентна: загрузка/запись data/finance-audit-history.json в audit-agent.ts (порт из удалённого server/finance/audit.ts) - registerFinanceRoutes убран из server/routes/index.ts, registerDi2Routes остался на его месте - Клиент: удалены pages/finance/, components/finance/, hooks/useFinance.ts, hooks/useColumnLabels.ts, lib/format.ts, pages/salary.tsx; роуты /finance*, /income*, /expenses*, /salary и lazy-импорты убраны из App.tsx - Навигация: «Финансы» в Sidebar и MobileBottomNav ведёт на субапп /di2 (внешняя ссылка), гейт moduleAccess.finance + finance.view сохранён - index.css: удалён блок .hover-elevate/.hover-elevate-2/.active-elevate (использовался только старым модульным DataTable) - FinanceBottomNav.tsx сохранён как референс для этапа 3 (.superpowers/sdd/di2-restyle/FinanceBottomNav.reference.tsx) - Не тронуто: client-di2/, сиды прав finance.view/manage, finance_access (module-access)
54 lines
2.1 KiB
TypeScript
54 lines
2.1 KiB
TypeScript
// Планировщик аудита целостности DI2: ежедневно в 03:00 (МСК) + первый запуск через 10 сек после старта.
|
||
// Порт из удалённого server/finance/audit.ts (без node-cron, setInterval-проверка).
|
||
|
||
import { runIntegrityAudit, getAuditHistory } from "./audit-agent";
|
||
|
||
const TICK_MS = 60_000;
|
||
const MSK_OFFSET_MS = 3 * 60 * 60 * 1000; // МСК = UTC+3
|
||
const AUDIT_HOUR_MSK = 3;
|
||
|
||
let lastRunDay: string | null = getAuditHistory()[0]?.timestamp
|
||
? new Date(new Date(getAuditHistory()[0].timestamp).getTime() + MSK_OFFSET_MS).toISOString().slice(0, 10)
|
||
: null;
|
||
let schedulerStarted = false;
|
||
|
||
function mskToday(): string {
|
||
return new Date(Date.now() + MSK_OFFSET_MS).toISOString().slice(0, 10);
|
||
}
|
||
|
||
function mskHourNow(): number {
|
||
return new Date(Date.now() + MSK_OFFSET_MS).getUTCHours();
|
||
}
|
||
|
||
async function tick(): Promise<void> {
|
||
const today = mskToday();
|
||
if (lastRunDay === today) return; // сегодня уже запускался
|
||
if (mskHourNow() < AUDIT_HOUR_MSK) return; // ещё не наступило 03:00 по МСК
|
||
lastRunDay = today;
|
||
try {
|
||
await runIntegrityAudit();
|
||
} catch (err) {
|
||
console.error("[AUDIT-AGENT] Ошибка при выполнении аудита:", err);
|
||
}
|
||
}
|
||
|
||
export function startAuditScheduler(): void {
|
||
if (schedulerStarted) return; // защита от повторной регистрации роутов (тесты и т.п.)
|
||
schedulerStarted = true;
|
||
|
||
setInterval(() => {
|
||
tick().catch((err) => console.error("[AUDIT-AGENT] Tick error:", err));
|
||
}, TICK_MS);
|
||
console.log("[AUDIT-AGENT] Планировщик запущен (ежедневно в 03:00 по МСК)");
|
||
|
||
// Первый запуск через 10 секунд после старта сервера
|
||
setTimeout(async () => {
|
||
try {
|
||
const result = await runIntegrityAudit();
|
||
lastRunDay = new Date(new Date(result.timestamp).getTime() + MSK_OFFSET_MS).toISOString().slice(0, 10);
|
||
} catch (err) {
|
||
console.error("[AUDIT-AGENT] Ошибка при начальном аудите:", err);
|
||
}
|
||
}, 10000);
|
||
}
|