From 619385981c250a881b1619b1901cfe5357ebaecf Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 1 Aug 2026 01:16:26 +0700 Subject: [PATCH] =?UTF-8?q?fix(integrations):=20=D0=BC=D0=B0=D1=82=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20CFDM-=D0=B4=D0=BE=D0=BC=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=BF=D0=BE=20dns=20=D0=B8=20CNAME-=D1=86=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Плашка «без привязки» оставалась для CNAME: sync шёл, но матч был только по IP. Co-authored-by: Cursor --- packages/db/src/repositories/vps-domains.ts | 62 ++++++++++++++++++- .../shared/src/contracts/integration-cfdm.ts | 2 + 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/db/src/repositories/vps-domains.ts b/packages/db/src/repositories/vps-domains.ts index 1262ba2..5a89e49 100644 --- a/packages/db/src/repositories/vps-domains.ts +++ b/packages/db/src/repositories/vps-domains.ts @@ -14,6 +14,10 @@ function normalizeIp(ip: string): string { return ip.trim().toLowerCase() } +function normalizeHost(host: string): string { + return host.trim().toLowerCase().replace(/\.+$/, '') +} + function collectVpsIps(vps: { ip?: string | null; additionalIps?: string[] }): string[] { const ips: string[] = [] if (vps.ip?.trim()) ips.push(normalizeIp(vps.ip)) @@ -41,6 +45,29 @@ function findVpsIdByIps( return null } +/** Точное совпадение hostname с полем VPS.dns. */ +function findVpsIdByDns( + allVps: ReturnType, + hostname: string, +): string | null { + const key = normalizeHost(hostname) + if (!key) return null + const matches = allVps.filter((v) => normalizeHost(v.dns ?? '') === key) + if (matches.length === 1) return matches[0]!.id + return null +} + +function findVpsIdForBinding( + allVps: ReturnType, + item: Pick, +): string | null { + return ( + findVpsIdByIps(allVps, item.ips) ?? + findVpsIdByDns(allVps, item.fqdn) ?? + (item.cnameTarget ? findVpsIdByDns(allVps, item.cnameTarget) : null) + ) +} + function resolveMatchStatus(vpsId: string | null): 'matched' | 'unmatched' { return vpsId ? 'matched' : 'unmatched' } @@ -107,6 +134,9 @@ export const vpsDomainsRepository = { if (!vpsId && storedIps.length > 0) { vpsId = findVpsIdByIps(allVps, storedIps) } + if (!vpsId) { + vpsId = findVpsIdByDns(allVps, row.fqdn) + } const matchStatus = vpsId && vpsIds.has(vpsId) ? 'matched' @@ -143,6 +173,7 @@ export const vpsDomainsRepository = { let deleted = 0 let upserted = 0 const keptBindingIds = new Set() + const fqdnToVpsId = new Map() for (const item of items) { if (item.deleted) { @@ -152,10 +183,14 @@ export const vpsDomainsRepository = { keptBindingIds.add(item.bindingId) - const vpsId = findVpsIdByIps(allVps, item.ips) + const vpsId = findVpsIdForBinding(allVps, item) const matchStatus = resolveMatchStatus(vpsId) - if (matchStatus === 'matched') matched++ - else unmatched++ + if (matchStatus === 'matched') { + matched++ + fqdnToVpsId.set(normalizeHost(item.fqdn), vpsId!) + } else { + unmatched++ + } const existing = this.getByCfdmBindingId(item.bindingId) const values = { @@ -182,6 +217,27 @@ export const vpsDomainsRepository = { upserted++ } + // CNAME → уже matched FQDN (например imsk → ihome, а ihome привязан по IP/dns) + let inherited = true + while (inherited) { + inherited = false + for (const item of items) { + if (item.deleted || !item.cnameTarget) continue + const existing = this.getByCfdmBindingId(item.bindingId) + if (!existing || existing.vpsId) continue + const parentVpsId = fqdnToVpsId.get(normalizeHost(item.cnameTarget)) + if (!parentVpsId) continue + db.update(schema.vpsDomains) + .set({ vpsId: parentVpsId, matchStatus: 'matched' }) + .where(eq(schema.vpsDomains.id, existing.id)) + .run() + fqdnToVpsId.set(normalizeHost(item.fqdn), parentVpsId) + matched++ + unmatched = Math.max(0, unmatched - 1) + inherited = true + } + } + if (opts?.fullSync) { const rows = db .select() diff --git a/packages/shared/src/contracts/integration-cfdm.ts b/packages/shared/src/contracts/integration-cfdm.ts index 6b90be1..55dfd41 100644 --- a/packages/shared/src/contracts/integration-cfdm.ts +++ b/packages/shared/src/contracts/integration-cfdm.ts @@ -9,6 +9,8 @@ export const cfdmBindingSyncItemSchema = z.object({ zoneName: z.string().min(1), hostname: z.string(), ips: z.array(z.string()), + /** CNAME-цель (FQDN), если binding — CNAME; для матчинга по dns / цепочке. */ + cnameTarget: z.string().optional(), deleted: z.boolean().optional(), })