34 lines
3.3 KiB
TypeScript
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
|
|
}
|