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

39 lines
2.6 KiB
TypeScript

// No database or RouterOS imports: this boundary can be checked with injected readers.
type PathServer = {id: number | string; type: string; enabled: boolean}
export async function collectConfirmedInternetPath<S extends PathServer, G, F, P, R extends object, W extends object>(sampledAt: string, readers: {
servers(): Promise<S[]>; gre(): Promise<G>; filters(): Promise<F>; speed(): Promise<P>
route(id: number): Promise<R | null>; wan(id: number): Promise<W | null>
}) {
const servers = await readers.servers()
const homes = servers.filter(s => s.enabled && s.type === "home-router")
const [greTunnels, filtersRulesets, speedProbes] = await Promise.all([readers.gre(), readers.filters(), readers.speed()])
const routeLookupByServerId: Record<string, R> = {}
const wanRuntimeByHomeId: Record<string, W> = {}
for (const home of homes) {
const [route, wan] = await Promise.all([readers.route(Number(home.id)), readers.wan(Number(home.id))])
if (route == null || wan == null) throw new Error("Internet path: устройство " + home.id + " не подтвердило route/WAN")
routeLookupByServerId[String(home.id)] = route
wanRuntimeByHomeId[String(home.id)] = wan
}
return {collectionComplete: true as const, sampledAt, servers, greTunnels, filtersRulesets, speedProbes, routeLookupByServerId, wanRuntimeByHomeId}
}
export function readInternetPathRows(raw: unknown, source: string, requiredFields: string[] = []): Array<Record<string, string>> {
if (!Array.isArray(raw) || raw.some(row => !row || typeof row !== "object" || Array.isArray(row) || Object.values(row).some(value => typeof value !== "string") || requiredFields.some(field => typeof row[field] !== "string" || !row[field].trim()))) {
throw new Error("Internet path: повреждён ответ " + source)
}
return raw as Array<Record<string, string>>
}
export function readStoredInternetPathPayload(raw: unknown): object {
const payload: unknown = typeof raw === "string" ? JSON.parse(raw) : raw
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Сохранённый снимок Internet path повреждён")
return payload
}
/** PostgreSQL string-mode timestamps must be normalized at the HTTP boundary. */
export function mapStoredInternetPathResponse(row: { payloadJson: unknown; sampledAt: string }) {
if (typeof row.sampledAt !== "string" || !row.sampledAt.trim()) throw new Error("Сохранённая дата Internet path повреждена")
return { snapshot: readStoredInternetPathPayload(row.payloadJson), sampledAt: new Date(row.sampledAt).toISOString() }
}