feat(topology): добавить группы сервисов CFDM на схему
Docker / build (push) Failing after 20s

Сервис ставится существующей группой с Badge типа резервирования; sync сохраняет lbMode.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-01 11:30:40 +07:00
co-authored by Cursor
parent 29325179f4
commit 4dc5609f17
15 changed files with 711 additions and 63 deletions
@@ -49,6 +49,39 @@ describe('vpsDomainsRepository', () => {
expect(domains).toHaveLength(1)
expect(domains[0]?.fqdn).toBe('vpn.example.com')
expect(domains[0]?.matchStatus).toBe('matched')
expect(domains[0]?.lbMode).toBeNull()
})
it('сохраняет lbMode из binding', () => {
const vps = vpsRepository.create({
ip: '203.0.113.10',
providerId: 'p1',
providerAccountId: 'a1',
status: 'active',
tariffType: 'monthly',
currency: 'RUB',
vcpu: 1,
ramGb: 1,
diskGb: 10,
})
const created = Array.isArray(vps) ? vps[0]! : vps
vpsDomainsRepository.syncBindings([
{
bindingId: 1,
serviceId: 10,
serviceName: 'VPN Node',
serviceSlug: 'vpn-node',
fqdn: 'vpn.example.com',
zoneName: 'example.com',
hostname: 'vpn',
ips: ['203.0.113.10'],
lbMode: 'failover',
},
])
const domains = vpsDomainsRepository.listByVpsId(created.id)
expect(domains[0]?.lbMode).toBe('failover')
})
it('помечает unmatched без совпадения IP', () => {
@@ -63,6 +63,7 @@ type BindingFields = {
cfdmBindingId: number
source: 'cfdm'
targetIps: string
lbMode: string | null
syncedAt: string
}
@@ -239,6 +240,7 @@ export const vpsDomainsRepository = {
cfdmBindingId: seed.cfdmBindingId,
source: 'cfdm',
targetIps: seed.targetIps ?? JSON.stringify(storedIps),
lbMode: seed.lbMode ?? null,
syncedAt: now,
}
const result = reconcileBindingToVpsIds(db, group, fields, wanted, emptyStatus)
@@ -282,6 +284,7 @@ export const vpsDomainsRepository = {
cfdmBindingId: item.bindingId,
source: 'cfdm',
targetIps: JSON.stringify(item.ips.filter(isIpLiteral)),
lbMode: item.lbMode ?? null,
syncedAt: now,
})
+9 -2
View File
@@ -262,6 +262,7 @@ const CORE_TABLE_MIGRATIONS: string[] = [
source TEXT NOT NULL DEFAULT 'cfdm',
matchStatus TEXT NOT NULL DEFAULT 'unmatched',
targetIps TEXT,
lbMode TEXT,
syncedAt TEXT NOT NULL,
UNIQUE(cfdmBindingId, vpsId)
)`,
@@ -376,6 +377,7 @@ const COLUMN_MIGRATIONS: string[] = [
`ALTER TABLE settings ADD COLUMN notifyLowBalanceEnabled INTEGER`,
`ALTER TABLE settings ADD COLUMN notifySyncDigestEnabled INTEGER`,
`ALTER TABLE vps_domains ADD COLUMN targetIps TEXT`,
`ALTER TABLE vps_domains ADD COLUMN lbMode TEXT`,
`ALTER TABLE providers ADD COLUMN spaceId TEXT`,
`ALTER TABLE provider_accounts ADD COLUMN spaceId TEXT`,
`ALTER TABLE provider_accounts ADD COLUMN balance_api REAL`,
@@ -473,6 +475,10 @@ function rebuildVpsDomainsBindingUnique(sqlite: Database.Database): void {
return
}
const colRows = sqlite.prepare(`PRAGMA table_info(vps_domains)`).all() as { name: string }[]
const hasLbMode = colRows.some((c) => c.name === 'lbMode')
const lbSelect = hasLbMode ? 'lbMode' : 'NULL'
sqlite.exec('PRAGMA foreign_keys = OFF')
sqlite.exec('BEGIN')
try {
@@ -490,15 +496,16 @@ function rebuildVpsDomainsBindingUnique(sqlite: Database.Database): void {
source TEXT NOT NULL DEFAULT 'cfdm',
matchStatus TEXT NOT NULL DEFAULT 'unmatched',
targetIps TEXT,
lbMode TEXT,
syncedAt TEXT NOT NULL,
UNIQUE(cfdmBindingId, vpsId)
)`)
sqlite.exec(`INSERT INTO vps_domains_new (
id, spaceId, vpsId, fqdn, zoneName, hostname, serviceName, serviceSlug,
cfdmServiceId, cfdmBindingId, source, matchStatus, targetIps, syncedAt
cfdmServiceId, cfdmBindingId, source, matchStatus, targetIps, lbMode, syncedAt
) SELECT
id, COALESCE(spaceId, 'space-main'), vpsId, fqdn, zoneName, hostname, serviceName, serviceSlug,
cfdmServiceId, cfdmBindingId, source, matchStatus, targetIps, syncedAt
cfdmServiceId, cfdmBindingId, source, matchStatus, targetIps, ${lbSelect}, syncedAt
FROM vps_domains`)
sqlite.exec('DROP TABLE vps_domains')
sqlite.exec('ALTER TABLE vps_domains_new RENAME TO vps_domains')
+1
View File
@@ -232,6 +232,7 @@ export const vpsDomains = sqliteTable(
source: text('source').notNull().default('cfdm'),
matchStatus: text('matchStatus').notNull().default('unmatched'),
targetIps: text('targetIps'),
lbMode: text('lbMode'),
syncedAt: text('syncedAt').notNull(),
},
(t) => ({