feat(vpn): монитор актуальности Яндекс Телемост + инструкция про выход из встречи

- INSTRUCTIONS_TEXT: добавлено требование выйти из встречи, а не завершать её
- vpn.db.ts: findAllActiveVpnTasks для поиска активных VPN-задач
- vpn.service.ts: checkVpnRoomsValidity — периодическая проверка комнат, concurrency=5
- vpn-bot.service.ts: findVpnBotConversationId и notifyVpnRoomExpired
- server/index.ts: worker каждые 6 часов, уведомление пользователю о протухшей ссылке
This commit is contained in:
2026-07-17 13:08:24 +03:00
parent 9f1c2e5b45
commit aea0e9c3fe
4 changed files with 195 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ import {
createVpnTask,
getVpnFormCache,
getFieldId,
findAllActiveVpnTasks,
type VpnFormCache,
} from './vpn.db';
import {
@@ -310,3 +311,46 @@ export async function recordTraffic(
return;
}
}
export interface ExpiredVpnRoom {
taskId: number;
organizationId: number;
createdBy: number;
roomUrl: string;
}
export async function checkVpnRoomsValidity(): Promise<ExpiredVpnRoom[]> {
const activeTasks = await findAllActiveVpnTasks();
const expired: ExpiredVpnRoom[] = [];
// Limit concurrent validations to avoid overwhelming Yandex API
const concurrency = 5;
for (let i = 0; i < activeTasks.length; i += concurrency) {
const batch = activeTasks.slice(i, i + concurrency);
const results = await Promise.all(
batch.map(async (task) => {
try {
const isValid = await validateTelemostRoom(task.roomUrl);
return { task, isValid };
} catch (err) {
console.error(`[VPN] validation error for task ${task.taskId}:`, err);
return { task, isValid: false };
}
}),
);
for (const { task, isValid } of results) {
if (!isValid) {
expired.push(task);
try {
await setVpnTaskStatus(task.taskId, 'Истекла', task.organizationId);
await setVpnTaskField(task.taskId, 'last_error', 'Комната Яндекс Телемост недоступна', task.organizationId);
} catch (err) {
console.error(`[VPN] failed to mark task ${task.taskId} as expired:`, err);
}
}
}
}
return expired;
}