Lead Radar: исходный код
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
9749da5ae7
201 files changed
+97685
No files matched your search
@@ -0,0 +1,59 @@
|
||||
import { and, desc, eq, isNotNull, isNull } from 'drizzle-orm';
|
||||
import type { Db } from '../db/client.ts';
|
||||
import { parserRuns, settings, users } from '../db/schema.ts';
|
||||
import { loadParserSettings } from '../parser/settings.ts';
|
||||
import { enqueueRaw } from '../telegram/outbox.ts';
|
||||
|
||||
// Сторож парсера: если парсер выключен или давно не запускался, суперадминам приходит сообщение
|
||||
// в Telegram; пока проблема не ушла — напоминание раз в несколько часов, после — «снова работает».
|
||||
// 20.10 парсер простоял почти двое суток выключенным, и это заметили только по пропавшему объекту.
|
||||
|
||||
/** Сколько минут без нового прохода считается «стоит». Глубокий проход идёт ~10 минут. */
|
||||
export const STALL_MINUTES = 20;
|
||||
const REMIND_HOURS = 6;
|
||||
|
||||
export interface WatchdogState {
|
||||
/** Когда отправлено последнее сообщение о простое; null — парсер в порядке. */
|
||||
alertedAt: Date | null;
|
||||
}
|
||||
|
||||
const ago = (from: Date, now: Date) => {
|
||||
const min = Math.round((now.getTime() - from.getTime()) / 60_000);
|
||||
return min < 120 ? `${min} мин` : `${Math.round(min / 60)} ч`;
|
||||
};
|
||||
|
||||
/** Что не так с парсером, или null, если всё в порядке. */
|
||||
export async function parserProblem(db: Db, now = new Date()): Promise<string | null> {
|
||||
const parser = await loadParserSettings(db);
|
||||
const stall = STALL_MINUTES * 60_000;
|
||||
if (!parser.enabled) {
|
||||
const [row] = await db.select({ at: settings.updatedAt }).from(settings).where(eq(settings.key, 'parser'));
|
||||
const since = row?.at ?? now;
|
||||
// Только что выключили руками — даём время включить обратно.
|
||||
return now.getTime() - since.getTime() >= stall ? `выключен в настройках (уже ${ago(since, now)})` : null;
|
||||
}
|
||||
const [last] = await db.select({ at: parserRuns.startedAt }).from(parserRuns).orderBy(desc(parserRuns.id)).limit(1);
|
||||
if (!last) return null;
|
||||
return now.getTime() - last.at.getTime() >= stall ? `не запускался ${ago(last.at, now)}` : null;
|
||||
}
|
||||
|
||||
/** Одна проверка: шлёт сообщение о простое (с напоминаниями) и о восстановлении. */
|
||||
export async function runParserWatchdog(db: Db, state: WatchdogState, now = new Date()): Promise<void> {
|
||||
const problem = await parserProblem(db, now);
|
||||
const remind = state.alertedAt && now.getTime() - state.alertedAt.getTime() >= REMIND_HOURS * 3_600_000;
|
||||
if (problem && (!state.alertedAt || remind)) {
|
||||
await notify(db, `⚠️ Парсер стоит: ${problem}. Новые объекты не приходят.\nВключить — Админка → Парсер.`);
|
||||
state.alertedAt = now;
|
||||
} else if (!problem && state.alertedAt) {
|
||||
await notify(db, '✅ Парсер снова работает.');
|
||||
state.alertedAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function notify(db: Db, text: string) {
|
||||
const admins = await db
|
||||
.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(and(eq(users.role, 'superadmin'), isNotNull(users.telegramId), isNull(users.removedAt)));
|
||||
await enqueueRaw(db, admins.map((a) => a.id), text);
|
||||
}
|
||||
Reference in new issue
Block a user