Files
MikrotikManager/backend/src/services/recursive-apply.ts
T

47 lines
2.2 KiB
TypeScript

import type { MikrotikClient } from "./mikrotik.js"
import { MikrotikError } from "./mikrotik.js"
import { parseRecursiveCatalog } from "./recursive-route-catalog.js"
export class RecursiveApplyError extends Error {
constructor(message: string, public readonly receipt: {
state: "partial" | "unknown" | "rejected" | "applied"
confirmedWrites: number
createdIds: string[]
deletedIds: string[]
needsReview: boolean
metadataRecorded: boolean
}) { super(message); this.name = "RecursiveApplyError" }
}
export async function executeRecursiveApply(
client: Pick<MikrotikClient, "get" | "put" | "delete">,
payloads: Record<string, string>[],
persist: () => Promise<void>,
) {
// The editor replaces every owned row: reject unsupported ownership before any write.
const existing = parseRecursiveCatalog(await client.get<unknown>("/ip/route"))
const createdIds: string[] = [], deletedIds: string[] = []
let nativeComplete = false
try {
for (const payload of payloads) {
const result = await client.put<unknown>("/ip/route", payload)
if (!result || typeof result !== "object" || !(".id" in result) || typeof result[".id"] !== "string" || !result[".id"].trim()) throw new Error("RouterOS не подтвердил ID созданного маршрута")
createdIds.push(result[".id"])
}
for (const route of existing) {
await client.delete("/ip/route/" + encodeURIComponent(route.id))
deletedIds.push(route.id)
}
nativeComplete = true
await persist()
return { pushed: createdIds.length, deleted: deletedIds.length }
} catch (error) {
const rejected = error instanceof MikrotikError && error.statusCode >= 400 && error.statusCode < 500
const confirmedWrites = createdIds.length + deletedIds.length
const state = nativeComplete ? "applied" : rejected ? (confirmedWrites ? "partial" : "rejected") : "unknown"
throw new RecursiveApplyError(`Подтверждено записей RouterOS: ${confirmedWrites}. Обновите каталог перед повторной попыткой. ${String(error)}`, {
state, confirmedWrites, createdIds, deletedIds, needsReview: state !== "rejected", metadataRecorded: false,
})
}
}