fix(db): писать null в пустые FK и мигрировать старые БД без apiType

Пустая строка в vpsId/providerAccountId/providerId давала 500
SQLITE_CONSTRAINT_FOREIGNKEY при создании платежа, записи баланса и VPS
без провайдера. Старые БД падали с «no such column apiType»: добавлены
колонки providers.apiType/apiBaseUrl и перенос URL BILLmanager с аккаунтов.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-11-06 22:22:37 +07:00
1 parent a64d8b9755
commit e37f69ebf0
7 files changed
+103 -16

No files matched your search

+7 -7
View File
@@ -71,8 +71,8 @@ export const migrateRoutes: FastifyPluginAsync = async (app) => {
v.ipv6 ?? '',
additionalIps,
v.dns ?? '',
v.providerId ?? '',
v.providerAccountId ?? '',
v.providerId || null,
v.providerAccountId || null,
v.country ?? '',
v.city ?? '',
v.datacenter ?? '',
@@ -88,7 +88,7 @@ export const migrateRoutes: FastifyPluginAsync = async (app) => {
v.purpose ?? '',
v.environment ?? '',
v.project ?? '',
v.projectId ?? null,
v.projectId || null,
v.monitoringEnabled ? 1 : 0,
v.backupEnabled ? 1 : 0,
v.status ?? 'active',
@@ -116,8 +116,8 @@ export const migrateRoutes: FastifyPluginAsync = async (app) => {
pm.date ?? '',
Number(pm.amount) || 0,
pm.currency ?? '',
pm.providerAccountId ?? '',
pm.vpsId ?? '',
pm.providerAccountId || null,
pm.vpsId || null,
pm.note ?? '',
)
}
@@ -136,8 +136,8 @@ export const migrateRoutes: FastifyPluginAsync = async (app) => {
Number(bl.amount) || 0,
bl.currency ?? '',
bl.direction ?? '',
bl.providerAccountId ?? '',
bl.vpsId ?? '',
bl.providerAccountId || null,
bl.vpsId || null,
bl.note ?? '',
)
}
+47
View File
@@ -0,0 +1,47 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { closeDb } from '@cfdm/db'
import { resetTestDb } from '@cfdm/db/test-setup'
import { buildApp } from '../index.js'
describe('payments / balance-ledger without VPS', () => {
let app: Awaited<ReturnType<typeof buildApp>>
beforeEach(async () => {
resetTestDb()
app = await buildApp()
})
afterEach(async () => {
await app.close()
closeDb()
})
it('POST /api/payments stores empty vpsId/providerAccountId as null', async () => {
const res = await app.inject({
method: 'POST',
url: '/api/payments',
payload: { type: 'topup', date: '2026-01-01', amount: 10, vpsId: '', providerAccountId: '' },
})
expect(res.statusCode).toBe(201)
const body = res.json() as { id: string; vpsId: string | null; providerAccountId: string | null }
expect(body.vpsId).toBeNull()
expect(body.providerAccountId).toBeNull()
const put = await app.inject({ method: 'PUT', url: `/api/payments/${body.id}`, payload: { note: 'x' } })
expect(put.statusCode).toBe(200)
})
it('POST /api/balance-ledger stores empty refs as null', async () => {
const res = await app.inject({
method: 'POST',
url: '/api/balance-ledger',
payload: { type: 'topup', date: '2026-01-01', amount: 10, vpsId: '', providerAccountId: '' },
})
expect(res.statusCode).toBe(201)
})
it('POST /api/vps without provider does not hit FK', async () => {
const res = await app.inject({ method: 'POST', url: '/api/vps', payload: { ip: '10.0.0.1' } })
expect(res.statusCode).toBeLessThan(300)
})
})