Files
MikrotikManager/backend/src/services/internet-path-read.ts
T

34 lines
3.3 KiB
TypeScript

import { pool } from "../db/index.js"
export interface StoredPath { id: number; sampledAt: string; payloadJson: unknown }
// Field names and selectors are internal constants. Server IDs are bound values.
const selectedArray = (field: string, selector: string) => `CASE WHEN jsonb_typeof(payload_json->'${field}')='array' THEN
(SELECT coalesce(jsonb_agg(value ORDER BY ord),'[]'::jsonb) FROM jsonb_array_elements(payload_json->'${field}') WITH ORDINALITY AS items(value,ord)
WHERE ${selector}) ELSE NULL END`
const selectedMap = (field: string) => `CASE WHEN jsonb_typeof(payload_json->'${field}')='object' THEN
(SELECT coalesce(jsonb_object_agg(key,value),'{}'::jsonb) FROM jsonb_each(payload_json->'${field}') WHERE key=ANY($1::text[])) ELSE NULL END`
const bindingValid = (field:string,keys:string[]) => `CASE WHEN jsonb_typeof(payload_json->'${field}')='array' THEN
NOT EXISTS(SELECT 1 FROM jsonb_array_elements(payload_json->'${field}') AS items(value) WHERE jsonb_typeof(value)<>'object'
OR ${keys.map(key=>`coalesce(value->>'${key}','') !~ '^[1-9][0-9]*$'`).join(" OR ")}) ELSE false END`
const mapBindingsValid = (field:string) => `CASE WHEN jsonb_typeof(payload_json->'${field}')='object' THEN
NOT EXISTS(SELECT 1 FROM jsonb_object_keys(payload_json->'${field}') AS keys(key) WHERE key !~ '^[1-9][0-9]*$') ELSE false END`
/** Shared latest-row source. Scoped calls project in SQL, before JSON is parsed
* by application models; malformed hidden records never reach that boundary. */
export async function readLatestInternetPathSnapshot(serverIds?: number[]): Promise<StoredPath | null> {
if (serverIds === undefined) return (await pool.query<StoredPath>('SELECT id,sampled_at::text AS "sampledAt",payload_json AS "payloadJson" FROM internet_path_snapshots ORDER BY id DESC,sampled_at DESC LIMIT 1')).rows[0] ?? null
if (!serverIds.length) return null
if (serverIds.some(id => !Number.isSafeInteger(id) || id <= 0)) throw new Error("Некорректная область Internet path")
const projection = `jsonb_build_object(
'bindingShapeValid',(${bindingValid("servers",["id"])} AND ${bindingValid("greTunnels",["serverId"])} AND ${bindingValid("filtersRulesets",["serverId"])}
AND ${bindingValid("speedProbes",["srcServerId","dstServerId"])} AND ${mapBindingsValid("routeLookupByServerId")} AND ${mapBindingsValid("wanRuntimeByHomeId")}),
'collectionComplete',payload_json->'collectionComplete','sampledAt',payload_json->'sampledAt','internetTarget',payload_json->'internetTarget',
'servers',${selectedArray("servers", "value->>'id'=ANY($1::text[])")},
'greTunnels',${selectedArray("greTunnels", "value->>'serverId'=ANY($1::text[])")},
'filtersRulesets',${selectedArray("filtersRulesets", "value->>'serverId'=ANY($1::text[])")},
'speedProbes',${selectedArray("speedProbes", "value->>'srcServerId'=ANY($1::text[]) AND value->>'dstServerId'=ANY($1::text[])")},
'routeLookupByServerId',${selectedMap("routeLookupByServerId")},'wanRuntimeByHomeId',${selectedMap("wanRuntimeByHomeId")})`
return (await pool.query<StoredPath>(`SELECT id,sampled_at::text AS "sampledAt",${projection} AS "payloadJson"
FROM (SELECT id,sampled_at,payload_json FROM internet_path_snapshots ORDER BY id DESC,sampled_at DESC LIMIT 1) latest`, [serverIds.map(String)])).rows[0] ?? null
}