fix(alerts): сохранять параметры выборочно и проверять результат отправки
This commit is contained in:
1 parent
0bcac655ea
commit
98319c9d50
11 files changed
+90
-58
No files matched your search
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "npm run build -w @mmapp/contracts && tsc",
|
||||
"test:alerts-telegram": "tsx src/services/alerts-telegram-settings.test.ts && tsx src/services/alerts-telegram-send.test.ts",
|
||||
"test:alert-engine": "tsx src/services/alert-engine/groups.test.ts && tsx src/services/alert-engine/evaluate-rule.test.ts && tsx src/services/alert-engine/confirm-stability.test.ts",
|
||||
"start": "node dist/index.js",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
@@ -27,7 +28,7 @@
|
||||
"test:scheduler": "tsx src/services/scheduler-recorded-run.test.ts && tsx src/routes/scheduler.test.ts",
|
||||
"test:backups": "tsx src/services/backup-scheduler-run.test.ts && tsx src/services/backup-restore.test.ts && tsx src/services/backup-deletion.test.ts && tsx src/services/backup-dto.test.ts && tsx src/services/backup-storage-update.test.ts && tsx src/services/backup-job-execution.test.ts && tsx src/routes/backups-operations.test.ts && tsx src/services/s3-backup-client.test.ts",
|
||||
"test:live-maps": "tsx src/services/ospf-route-parse.test.ts && tsx src/services/vxlan-live.test.ts && tsx src/services/containers-live.test.ts && tsx src/services/firewall-poll.test.ts && tsx src/services/gre-catalog-poll.test.ts && tsx src/routes/filters-gre-catalog.test.ts && tsx src/routes/bgp-catalog.test.ts && tsx src/services/bgp-catalog-parse.test.ts && tsx src/routes/ospf-catalog.test.ts && tsx src/services/ospf-observed-values.test.ts",
|
||||
"test": "npm run test:probes && npm run test:exec && npm run test:alert-engine && npm run test:auth && npm run test:wireguard && npm run test:traffic-rate && npm run test:traffic-flow && npm run test:users && npm run test:pg && npm run test:backups && npm run test:scheduler && npm run test:live-maps && npm run test:ospf-optimizer && npm run test:recursive-routes && npm run test:config-sync && npm run test:ros-file && npm run test:containers && npm run test:certificates && npm run test:netflow-settings && npm run test:netflow-purge && npm run test:evolution && npm run test:system-database",
|
||||
"test": "npm run test:probes && npm run test:exec && npm run test:alert-engine && npm run test:alerts-telegram && npm run test:auth && npm run test:wireguard && npm run test:traffic-rate && npm run test:traffic-flow && npm run test:users && npm run test:pg && npm run test:backups && npm run test:scheduler && npm run test:live-maps && npm run test:ospf-optimizer && npm run test:recursive-routes && npm run test:config-sync && npm run test:ros-file && npm run test:containers && npm run test:certificates && npm run test:netflow-settings && npm run test:netflow-purge && npm run test:evolution && npm run test:system-database",
|
||||
"test:evolution": "tsx src/services/evobgp-catalog.test.ts && tsx src/routes/evobgp-catalog.test.ts && tsx src/routes/evobgp-settings.test.ts && tsx src/plugins/auth.evolution-settings.test.ts",
|
||||
"test:netflow-purge": "tsx src/db/operation-lock.test.ts && tsx src/services/traffic-flow-purge-execution.test.ts && tsx src/routes/traffic-flow-purge.test.ts",
|
||||
"test:netflow-settings": "tsx src/services/traffic-flow-host-file-builders.test.ts && tsx src/services/traffic-flow-key-execution.test.ts && tsx src/routes/traffic-flow-settings.test.ts && tsx src/plugins/auth.netflow-settings.test.ts",
|
||||
|
||||
@@ -72,10 +72,11 @@ const alertsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
|
||||
app.put("/alerts/telegram", async (req, reply) => {
|
||||
const parsed = putTelegramSchema.safeParse(req.body)
|
||||
const parsed = putTelegramSchema.strict().safeParse(req.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
|
||||
}
|
||||
if (Object.keys(parsed.data).length === 0) return reply.status(400).send({error:"Нет изменённых параметров Telegram"})
|
||||
const next = await updateTelegramSettings({
|
||||
token: parsed.data.token,
|
||||
chatId: parsed.data.chatId,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {sendTelegramRequest} from "./alerts-telegram-send.js"
|
||||
import {executeTelegramSettingsUpdate} from "./alerts-telegram-settings.js"
|
||||
import { asc, desc, eq, sql } from "drizzle-orm"
|
||||
import { db, dbAll } from "../db/index.js"
|
||||
import {
|
||||
@@ -58,7 +60,7 @@ export type ApiAlertHistoryEntry = {
|
||||
async function ensureTelegramRow() {
|
||||
let row = (await db.select().from(alertTelegramSettings).where(eq(alertTelegramSettings.id, 1)).limit(1))[0]
|
||||
if (!row) {
|
||||
await db.insert(alertTelegramSettings).values({ id: 1 })
|
||||
await db.insert(alertTelegramSettings).values({ id: 1 }).onConflictDoNothing({target:alertTelegramSettings.id})
|
||||
row = (await db.select().from(alertTelegramSettings).where(eq(alertTelegramSettings.id, 1)).limit(1))[0]
|
||||
}
|
||||
return row!
|
||||
@@ -309,37 +311,13 @@ export async function updateTelegramSettings(patch: {
|
||||
/** undefined — не менять; null — сбросить тему */
|
||||
messageThreadId?: number | null
|
||||
}) {
|
||||
const cur = await ensureTelegramRow()
|
||||
let nextToken = cur.botToken
|
||||
let nextChat = cur.chatId ?? ""
|
||||
let nextThread: number | null | undefined = undefined
|
||||
|
||||
if (patch.token !== undefined) {
|
||||
if (patch.token === null || patch.token === "") nextToken = ""
|
||||
else nextToken = patch.token.trim()
|
||||
}
|
||||
if (patch.chatId !== undefined) nextChat = patch.chatId.trim()
|
||||
if (patch.messageThreadId !== undefined) {
|
||||
if (patch.messageThreadId === null) nextThread = null
|
||||
else {
|
||||
const n = Math.floor(patch.messageThreadId)
|
||||
nextThread = Number.isFinite(n) && n >= 1 ? n : null
|
||||
}
|
||||
}
|
||||
|
||||
const base = {
|
||||
botToken: nextToken,
|
||||
chatId: nextChat,
|
||||
updatedAt: sql`now()`,
|
||||
}
|
||||
if (nextThread !== undefined) {
|
||||
await db.update(alertTelegramSettings)
|
||||
.set({ ...base, messageThreadId: nextThread })
|
||||
.where(eq(alertTelegramSettings.id, 1))
|
||||
} else {
|
||||
await db.update(alertTelegramSettings).set(base).where(eq(alertTelegramSettings.id, 1))
|
||||
}
|
||||
return await getTelegramPublic()
|
||||
return executeTelegramSettingsUpdate(patch, {
|
||||
ensure: ensureTelegramRow,
|
||||
update: async values => {
|
||||
const [row] = await db.update(alertTelegramSettings).set({...values, updatedAt:sql`now()`}).where(eq(alertTelegramSettings.id,1)).returning()
|
||||
return row
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Токен только для внутреннего вызова Telegram API (не отдаётся клиенту). */
|
||||
@@ -395,26 +373,12 @@ export async function sendTelegramAlertMessage(opts: {
|
||||
? Math.floor(opts.messageThreadId)
|
||||
: undefined
|
||||
: await getTelegramMessageThreadIdForApi()
|
||||
const url = `https://api.telegram.org/bot${encodeURIComponent(token)}/sendMessage`
|
||||
try {
|
||||
const payload: { chat_id: string; text: string; message_thread_id?: number } = {
|
||||
chat_id: chatId,
|
||||
text: opts.text,
|
||||
}
|
||||
if (threadId != null) payload.message_thread_id = threadId
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
const data = (await res.json().catch(() => ({}))) as { ok?: boolean; description?: string }
|
||||
if (!res.ok || data.ok === false) {
|
||||
return { ok: false, error: data.description ?? res.statusText ?? "Telegram API error" }
|
||||
}
|
||||
return { ok: true }
|
||||
} catch (e) {
|
||||
return { ok: false, error: e instanceof Error ? e.message : "Ошибка сети" }
|
||||
const payload: { chat_id: string; text: string; message_thread_id?: number } = {
|
||||
chat_id: chatId,
|
||||
text: opts.text,
|
||||
}
|
||||
if (threadId != null) payload.message_thread_id = threadId
|
||||
return sendTelegramRequest(token,payload)
|
||||
}
|
||||
|
||||
export async function appendAlertHistory(entry: {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import assert from "node:assert/strict"
|
||||
import {test} from "node:test"
|
||||
import {sendTelegramRequest} from "./alerts-telegram-send.js"
|
||||
test("Telegram send requires HTTP success and literal true receipt; malformed body is never success",async()=>{
|
||||
for(const body of [{},{ok:false},{ok:"true"},null]){let calls=0;const result=await sendTelegramRequest("fixture-secret",{chat_id:"123",text:"fixture"},async()=>{calls++;return Response.json(body)});assert.equal(result.ok,false);assert.equal(calls,1)}
|
||||
assert.equal((await sendTelegramRequest("fixture-secret",{chat_id:"123",text:"fixture"},async()=>new Response("html"))).ok,false)
|
||||
assert.equal((await sendTelegramRequest("fixture-secret",{chat_id:"123",text:"fixture"},async()=>Response.json({ok:true},{status:503}))).ok,false)
|
||||
})
|
||||
test("Telegram send uses bounded single POST and preserves confirmed result",async()=>{
|
||||
let calls=0;const result=await sendTelegramRequest("fixture:secret",{chat_id:"123",text:"fixture",message_thread_id:1},async(url,init)=>{calls++;assert.equal(url,"https://api.telegram.org/botfixture%3Asecret/sendMessage");assert.equal(init?.method,"POST");assert.ok(init?.signal);assert.deepEqual(JSON.parse(init?.body as string),{chat_id:"123",text:"fixture",message_thread_id:1});return Response.json({ok:true})});assert.deepEqual(result,{ok:true});assert.equal(calls,1)
|
||||
})
|
||||
test("Telegram network unknown has no retry or secret; upstream error is redacted",async()=>{
|
||||
let calls=0;assert.deepEqual(await sendTelegramRequest("fixture:secret",{chat_id:"123",text:"fixture"},async()=>{calls++;throw Error("fixture:secret network URL")}),{ok:false,error:"Результат отправки в Telegram неизвестен: ответ не получен"});assert.equal(calls,1)
|
||||
const result=await sendTelegramRequest("fixture:secret",{chat_id:"123",text:"fixture"},async()=>Response.json({ok:false,description:"fixture:secret fixture%3Asecret"}));assert.equal(result.ok,false);if(!result.ok)assert.equal(result.error,"[TOKEN] [TOKEN]")
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
type Payload={chat_id:string;text:string;message_thread_id?:number}
|
||||
export async function sendTelegramRequest(token:string,payload:Payload,fetcher:typeof fetch=fetch):Promise<{ok:true}|{ok:false;error:string}>{
|
||||
const redact=(message:string)=>message.replaceAll(token,"[TOKEN]").replaceAll(encodeURIComponent(token),"[TOKEN]")
|
||||
try{
|
||||
const response=await fetcher("https://api.telegram.org/bot"+encodeURIComponent(token)+"/sendMessage",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload),signal:AbortSignal.timeout(30_000)})
|
||||
const raw:unknown=await response.json().catch(()=>undefined)
|
||||
if(response.ok&&typeof raw==="object"&&raw!==null&&"ok" in raw&&raw.ok===true)return {ok:true}
|
||||
const description=typeof raw==="object"&&raw!==null&&"description" in raw&&typeof raw.description==="string"?raw.description:"Telegram не подтвердил приём сообщения"
|
||||
return {ok:false,error:redact(description)}
|
||||
}catch{return {ok:false,error:"Результат отправки в Telegram неизвестен: ответ не получен"}}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import assert from "node:assert/strict"
|
||||
import {test} from "node:test"
|
||||
import {executeTelegramSettingsUpdate,type TelegramStoredPatch} from "./alerts-telegram-settings.js"
|
||||
function fixture(){let row={botToken:"fixture-secret",chatId:"old",messageThreadId:10 as number|null};const writes:TelegramStoredPatch[]=[];return {writes,get:()=>row,set:(next:typeof row)=>row=next,deps:{ensure:async()=>{},update:async(patch:TelegramStoredPatch)=>{writes.push(patch);row={...row,...patch};return {...row}}}}
|
||||
}
|
||||
test("Telegram selective update changes only submitted fields and does not expose secret",async()=>{const f=fixture();const publicFields=await executeTelegramSettingsUpdate({chatId:" new "},f.deps);assert.deepEqual(f.writes,[{chatId:"new"}]);assert.deepEqual(f.get(),{botToken:"fixture-secret",chatId:"new",messageThreadId:10});assert.deepEqual(publicFields,{chatId:"new",tokenConfigured:true,messageThreadId:10});assert.equal(JSON.stringify(publicFields).includes("fixture-secret"),false)})
|
||||
test("Telegram disjoint concurrent patches preserve token/chat/thread without stale full-row writes",async()=>{const f=fixture();await Promise.all([executeTelegramSettingsUpdate({token:null},f.deps),executeTelegramSettingsUpdate({messageThreadId:null},f.deps),executeTelegramSettingsUpdate({chatId:"new"},f.deps)]);assert.deepEqual(f.get(),{botToken:"",chatId:"new",messageThreadId:null});assert.equal(f.writes.length,3);assert.ok(f.writes.every(row=>Object.keys(row).length===1))})
|
||||
test("Telegram invalid/empty patch does not ensure or update",async()=>{let calls=0;const deps={ensure:async()=>{calls++},update:async()=>{calls++;return undefined}};for(const patch of [{},{messageThreadId:0},{messageThreadId:1.5},{messageThreadId:2147483648},{token:4}])await assert.rejects(executeTelegramSettingsUpdate(patch as never,deps),/Проверьте/);assert.equal(calls,0)})
|
||||
test("Telegram missing/mismatched returning row does not confirm save or retry",async()=>{for(const row of [undefined,{botToken:"secret",chatId:"old",messageThreadId:null}]){let calls=0;await assert.rejects(executeTelegramSettingsUpdate({chatId:"new"},{ensure:async()=>{},update:async()=>{calls++;return row}}),/не подтверждено/);assert.equal(calls,1)}})
|
||||
@@ -0,0 +1,20 @@
|
||||
import {putTelegramSchema,telegramPublicSchema,type PutTelegramBody} from "@mmapp/contracts/alerts"
|
||||
export type TelegramStoredRow={botToken:string;chatId:string;messageThreadId:number|null}
|
||||
export type TelegramStoredPatch=Partial<TelegramStoredRow>
|
||||
interface Dependencies {ensure:()=>Promise<unknown>;update:(patch:TelegramStoredPatch)=>Promise<TelegramStoredRow|undefined>}
|
||||
export async function executeTelegramSettingsUpdate(input:PutTelegramBody,deps:Dependencies){
|
||||
const parsed=putTelegramSchema.strict().safeParse(input)
|
||||
if(!parsed.success||Object.keys(parsed.data).length===0)throw Error("Проверьте изменённые параметры Telegram")
|
||||
const patch:TelegramStoredPatch={}
|
||||
if(parsed.data.token!==undefined)patch.botToken=parsed.data.token?.trim()??""
|
||||
if(parsed.data.chatId!==undefined)patch.chatId=parsed.data.chatId.trim()
|
||||
if(parsed.data.messageThreadId!==undefined)patch.messageThreadId=parsed.data.messageThreadId
|
||||
if(Object.keys(patch).length===0)throw Error("Нет изменённых параметров Telegram")
|
||||
await deps.ensure()
|
||||
const row=await deps.update(patch)
|
||||
if(!row||Object.entries(patch).some(([key,value])=>row[key as keyof TelegramStoredRow]!==value))throw Error("Сохранение параметров Telegram не подтверждено")
|
||||
const result=telegramPublicSchema.safeParse({chatId:row.chatId,tokenConfigured:Boolean(row.botToken.trim()),messageThreadId:row.messageThreadId,connected:Boolean(row.botToken.trim()&&row.chatId.trim())})
|
||||
if(!result.success)throw Error("Сохранённые параметры Telegram не подтверждены")
|
||||
const {connected:_connected,...publicFields}=result.data
|
||||
return publicFields
|
||||
}
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-03
|
||||
|
||||
Этап 11Б в работе; последний проверенный блок 11Б.4.1; System DB принят локально с внешними ограничениями. Следующий блок 11Б.4.2.1: backend Telegram selective atomic save/RETURNING, DI HTTP/JWT/send receipts; затем rules concurrency и Alerts UI. Затем Alerts/Releases/probe scheduling, приёмка 11Б и этап 12.
|
||||
Этап 11Б в работе; последний проверенный блок 11Б.4.2.1.1; System DB принят локально с внешними ограничениями. Следующий блок 11Б.4.2.1.2: DI Alerts HTTP/JWT read/write/save/test и согласованный источник Telegram; затем rules concurrency и Alerts UI. Затем Alerts/Releases/probe scheduling, приёмка 11Б и этап 12.
|
||||
|
||||
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md).
|
||||
|
||||
@@ -306,3 +306,5 @@ REST запрос → ожидание → подтверждённый output /
|
||||
2026-10-03: 11Б.3.6.2.3.2 — 6 source и 18 соседних E2E прошли; база принята в локальных границах. Далее 11Б.4.1 Alerts contracts/API.
|
||||
|
||||
2026-10-03: 11Б.4.1 — строгие Alerts output schemas/API и сверка save receipts; 5 unit/builds/typecheck/lint прошли. Module пока не подключён. Далее 11Б.4.2.1 backend Telegram.
|
||||
|
||||
2026-10-03: 11Б.4.2.1.1 — selective Telegram UPDATE RETURNING, strict send receipt; 7 DI/send tests/build/lint прошли. confirm-stability пропущен без PG. Далее HTTP/JWT 11Б.4.2.1.2.
|
||||
@@ -28,7 +28,7 @@
|
||||
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.4.1; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.4.2.1.1; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
|
||||
|
||||
|
||||
## Блок 11Б.4.2.1.1 — selective Telegram save и подтверждение отправки (2026-10-03)
|
||||
|
||||
- updateTelegramSettings больше не перезаписывает token/chat из предварительно прочитанного snapshot: строит только переданные columns, SQL UPDATE RETURNING подтверждает запись и сохраняет остальные поля. Singleton insert conflict-safe. DI helper проверяет patch/returning, public response не возвращает secret. Route отклоняет unknown/empty patch до update; thread ID ограничен положительным PostgreSQL int4 вместо silent floor/overflow.
|
||||
- Send helper требует HTTP success и literal ok:true, HTML/{}/false/строка true не подтверждают приём Telegram. Один POST, 30s signal, без повтора внутри helper. При отсутствии ответа результат неизвестен; raw network error не раскрывается, upstream description редактирует literal/URL-encoded token. Подтверждение приёма API не является доказательством доставки получателю; существующая outbox retry policy не изменялась и ещё требует отдельного аудита unknown результатов.
|
||||
- 4 DI save + 3 send-response tests, backend build/профильный lint прошли; 5 frontend API unit из предыдущего блока сохраняют совместимость. Alert-engine groups/evaluate tests прошли, confirm-stability пропущен из-за отсутствия PostgreSQL — не считать проверенным. Реальные DB concurrency/Telegram не запускались.
|
||||
- Следующий блок 11Б.4.2.1.2: DI реальных Alerts HTTP handlers + signed JWT read/write gates и save/test receipts, согласованный источник Telegram для test. Затем rules transaction/readback concurrency и UI known/drafts/source/rights/adaptive. UI пока использует прежний generic API.
|
||||
|
||||
## Блок 11Б.4.1 — строгие Alerts contracts/API (2026-10-03)
|
||||
|
||||
- Аудит существующего UI: GET/PUT/POST принимают unchecked JSON; load заменяет Telegram draft и маскирует вторичные каталоги пустыми, save error вызывает load; целевая metadata иногда подменяется demo значениями. UI/backend пока не мигрированы этим блоком и не приняты.
|
||||
|
||||
@@ -236,3 +236,5 @@ ReUI PRO/base-nova; существующие Frame, Data Grid v9, KpiStatGrid, S
|
||||
Завершён 11Б.3.6.2.3.2: 6 source/ABA/reload и 18 General/DB E2E прошли, DB принят локально. Реальный PG/UDP worker/Portal не принят. Далее 11Б.4.1 — strict Alerts contracts/API, затем backend/UI.
|
||||
|
||||
Завершён 11Б.4.1: строгие Alerts output/API/receipt сверки, 5 unit/contracts/backend build/typecheck/lint прошли. UI/backend пока прежние. Далее 11Б.4.2.1 — atomic selective Telegram save/RETURNING и DI HTTP/JWT/test results; затем rules/UI.
|
||||
|
||||
Завершён 11Б.4.2.1.1: selective Telegram UPDATE RETURNING/partial concurrent patches, singleton conflict-safe и strict bounded send receipt; 7 tests/build/lint прошли. Groups/evaluate regression прошла, confirm-stability skip без PG. Далее 11Б.4.2.1.2 — Alerts real HTTP/JWT/save/test и согласованный источник; UI прежний.
|
||||
@@ -58,7 +58,7 @@ export const putRulesSchema = z.object({
|
||||
export const putTelegramSchema = z.object({
|
||||
token: z.union([z.string(), z.null()]).optional(),
|
||||
chatId: z.string().optional(),
|
||||
messageThreadId: z.union([z.number().int().positive(), z.null()]).optional(),
|
||||
messageThreadId: z.union([z.number().int().positive().max(2147483647), z.null()]).optional(),
|
||||
})
|
||||
|
||||
export const rulePreviewTelegramSchema = z.object({
|
||||
@@ -72,7 +72,7 @@ export const rulePreviewTelegramSchema = z.object({
|
||||
export const testTelegramSchema = z.object({
|
||||
token: z.string().optional(),
|
||||
chatId: z.string().optional(),
|
||||
messageThreadId: z.coerce.number().int().positive().optional(),
|
||||
messageThreadId: z.coerce.number().int().positive().max(2147483647).optional(),
|
||||
rulePreview: rulePreviewTelegramSchema.optional(),
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ export type TestTelegramBody = z.infer<typeof testTelegramSchema>
|
||||
// Public response schemas: configured credentials do not prove Telegram connectivity/delivery.
|
||||
const alertTimestampSchema = z.string().min(1).refine(value => Number.isFinite(Date.parse(value)), "Некорректное время оповещения")
|
||||
export const telegramPublicSchema = z.object({
|
||||
chatId:z.string(),tokenConfigured:z.boolean(),connected:z.boolean(),messageThreadId:z.number().int().positive().nullable(),
|
||||
chatId:z.string(),tokenConfigured:z.boolean(),connected:z.boolean(),messageThreadId:z.number().int().positive().max(2147483647).nullable(),
|
||||
}).strict().refine(value => value.connected === (value.tokenConfigured && Boolean(value.chatId.trim())), "Противоречивое состояние параметров Telegram")
|
||||
export const alertRuleResponseSchema = z.object({
|
||||
id:z.string().min(1),name:z.string().min(1),type:alertTypeSchema,target:z.string(),targets:z.array(z.string().min(1)),groupId:z.string().nullable(),condition:z.string(),conditions:z.array(z.string().min(1)),severity:alertSeveritySchema,enabled:z.boolean(),cooldown:alertCooldownSchema,
|
||||
|
||||
Reference in new issue
Block a user