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

@@ -290,3 +290,79 @@ export async function updateVpnTaskFieldMap(
await setVpnTaskField(taskId, code, value, organizationId);
}
}
export interface ActiveVpnTaskInfo {
taskId: number;
organizationId: number;
createdBy: number;
roomUrl: string;
}
export async function findAllActiveVpnTasks(): Promise<ActiveVpnTaskInfo[]> {
// Find all organizations that have VPN tasks
const orgRows = await db
.select({ orgId: tasks.organizationId })
.from(tasks)
.where(eq(tasks.formId, VPN_FORM_ID))
.groupBy(tasks.organizationId);
const results: ActiveVpnTaskInfo[] = [];
for (const { orgId } of orgRows) {
try {
const cache = await getVpnFormCache(orgId);
const statusFieldId = getFieldId(cache, 'status');
const activeFieldId = getFieldId(cache, 'is_active');
const roomUrlFieldId = getFieldId(cache, 'room_url');
const rows = await db
.select({
taskId: tasks.id,
createdBy: tasks.createdBy,
roomUrl: taskFieldValues.value,
})
.from(tasks)
.innerJoin(
taskFieldValues,
and(
eq(taskFieldValues.taskId, tasks.id),
eq(taskFieldValues.formId, VPN_FORM_ID),
eq(taskFieldValues.fieldId, roomUrlFieldId),
),
)
.where(
and(
eq(tasks.formId, VPN_FORM_ID),
eq(tasks.organizationId, orgId),
sql`exists (
select 1 from task_field_values tfv
where tfv.task_id = tasks.id
and tfv.field_id = ${statusFieldId}
and tfv.value = to_jsonb('Активна'::text)
)`,
sql`exists (
select 1 from task_field_values tfv
where tfv.task_id = tasks.id
and tfv.field_id = ${activeFieldId}
and tfv.value = to_jsonb(true)
)`,
),
);
for (const row of rows) {
if (typeof row.roomUrl === 'string' && row.roomUrl) {
results.push({
taskId: row.taskId,
organizationId: orgId,
createdBy: row.createdBy,
roomUrl: row.roomUrl,
});
}
}
} catch (err) {
console.error(`[VPN DB] findAllActiveVpnTasks error for org ${orgId}:`, err);
}
}
return results;
}