Files
vps-tracker/apps/api/src/services/backup-import.ts
T
DenozordecandCursor 9e0311b53a
Docker / build (push) Failing after 26s
feat(settings): добавить Bot API URL и ячейку ошибок уведомлений
Монитор больше не ставит «Внимание» по скрытому счётчику. Telegram можно слать через локальный telegram-bot-api.

Co-authored-by: Cursor <[email protected]>
2026-08-22 02:29:07 +07:00

287 lines
11 KiB
TypeScript

import { getDb, getSqlite, schema, consolidateAllProviderApiSources } from '@cfdm/db'
export interface BackupPayload {
providers?: unknown[]
serverProjects?: unknown[]
providerAccounts?: unknown[]
settings?: unknown[] | unknown
vps?: unknown[]
payments?: unknown[]
balanceLedger?: unknown[]
activeTariffs?: unknown[]
tariffSyncOptions?: unknown[]
syncLog?: unknown[]
}
function asObject(v: unknown): Record<string, unknown> {
return typeof v === 'object' && v !== null ? (v as Record<string, unknown>) : {}
}
export function importJsonSnapshot(data: BackupPayload): void {
const db = getDb()
const sqlite = getSqlite()
sqlite.exec('BEGIN')
try {
db.delete(schema.syncLog).run()
db.delete(schema.tariffSyncOptions).run()
db.delete(schema.activeTariffs).run()
db.delete(schema.balanceLedger).run()
db.delete(schema.payments).run()
db.delete(schema.vps).run()
db.delete(schema.providerAccounts).run()
db.delete(schema.serverProjects).run()
db.delete(schema.providers).run()
db.delete(schema.settings).run()
for (const raw of Array.isArray(data.providers) ? data.providers : []) {
const p = asObject(raw)
db.insert(schema.providers)
.values({
id: String(p.id ?? ''),
name: String(p.name ?? ''),
website: String(p.website ?? ''),
contact: String(p.contact ?? ''),
baseCurrency: String(p.baseCurrency ?? ''),
usdRate: String(p.usdRate ?? ''),
eurRate: String(p.eurRate ?? ''),
notes: String(p.notes ?? ''),
apiType: String(p.apiType ?? ''),
apiBaseUrl: String(p.apiBaseUrl ?? ''),
})
.run()
}
for (const raw of Array.isArray(data.serverProjects) ? data.serverProjects : []) {
const sp = asObject(raw)
db.insert(schema.serverProjects)
.values({
id: String(sp.id ?? ''),
name: String(sp.name ?? ''),
color: sp.color != null ? String(sp.color) : null,
sortOrder: Number(sp.sortOrder) || 0,
notes: sp.notes != null ? String(sp.notes) : null,
createdAt: sp.createdAt != null ? String(sp.createdAt) : null,
})
.run()
}
for (const raw of Array.isArray(data.providerAccounts) ? data.providerAccounts : []) {
const acc = asObject(raw)
const alertRaw = acc.balance_alert_below ?? acc.balanceAlertBelow
const alertBelow =
alertRaw != null && alertRaw !== '' && Number.isFinite(Number(alertRaw)) ? Number(alertRaw) : null
db.insert(schema.providerAccounts)
.values({
id: String(acc.id ?? ''),
providerId: String(acc.providerId ?? ''),
name: String(acc.name ?? ''),
panelUrl: String(acc.panelUrl ?? ''),
currency: String(acc.currency ?? ''),
billingMode: String(acc.billingMode ?? ''),
notes: String(acc.notes ?? ''),
apiType: String(acc.apiType ?? ''),
apiBaseUrl: String(acc.apiBaseUrl ?? ''),
apiCredentials: String(acc.apiCredentials ?? ''),
balanceApi: acc.balance_api != null ? Number(acc.balance_api) : acc.balanceApi != null ? Number(acc.balanceApi) : null,
balanceCurrency:
acc.balance_currency != null ? String(acc.balance_currency) : acc.balanceCurrency != null ? String(acc.balanceCurrency) : null,
balanceUpdatedAt:
acc.balance_updated_at != null ? String(acc.balance_updated_at) : acc.balanceUpdatedAt != null ? String(acc.balanceUpdatedAt) : null,
enoughmoneyto: acc.enoughmoneyto != null ? String(acc.enoughmoneyto) : null,
balanceAlertBelow: alertBelow,
})
.run()
}
consolidateAllProviderApiSources(sqlite)
const settingsList = Array.isArray(data.settings)
? data.settings
: data.settings
? [data.settings]
: []
for (const raw of settingsList) {
const s = asObject(raw)
let customFields = s.customFields
if (Array.isArray(customFields)) customFields = JSON.stringify(customFields)
db.insert(schema.settings)
.values({
id: String(s.id ?? 'settings-main'),
baseCurrency: String(s.baseCurrency ?? 'RUB'),
ratesUrl: String(s.ratesUrl ?? ''),
autoConvert: s.autoConvert !== false && s.autoConvert !== 0 ? 1 : 0,
ratesUpdatedAt: String(s.ratesUpdatedAt ?? ''),
syncEnabled: s.syncEnabled ? 1 : 0,
syncIntervalMinutes: Number(s.syncIntervalMinutes) || 60,
syncTariffsIntervalMinutes: Number(s.syncTariffsIntervalMinutes) || 1440,
telegramBotToken: String(s.telegramBotToken ?? ''),
telegramChatId: String(s.telegramChatId ?? ''),
telegramApiUrl: String(s.telegramApiUrl ?? ''),
telegramMessageThreadId: String(s.telegramMessageThreadId ?? ''),
notifyPaymentExpiryEnabled: s.notifyPaymentExpiryEnabled ? 1 : 0,
notifyNewTariffsEnabled: s.notifyNewTariffsEnabled ? 1 : 0,
customFields: customFields != null ? String(customFields) : null,
notifyLowBalanceEnabled: s.notifyLowBalanceEnabled ? 1 : 0,
notifySyncDigestEnabled: s.notifySyncDigestEnabled ? 1 : 0,
notifyVpsDownEnabled: s.notifyVpsDownEnabled ? 1 : 0,
webhookUrl: String(s.webhookUrl ?? ''),
webhookEnabled: s.webhookEnabled ? 1 : 0,
notifyIntervalMinutes: Number(s.notifyIntervalMinutes) || 60,
uptimeCheckIntervalMinutes: Number(s.uptimeCheckIntervalMinutes) || 5,
})
.run()
}
for (const raw of Array.isArray(data.vps) ? data.vps : []) {
const v = asObject(raw)
const additionalIps = Array.isArray(v.additionalIps) ? JSON.stringify(v.additionalIps) : '[]'
const dailyRate = v.dailyRate === '' || v.dailyRate == null ? null : Number(v.dailyRate)
const monthlyRate = v.monthlyRate === '' || v.monthlyRate == null ? null : Number(v.monthlyRate)
const userOverrides = Array.isArray(v.userOverrides)
? JSON.stringify(v.userOverrides)
: String(v.userOverrides ?? '[]')
db.insert(schema.vps)
.values({
id: String(v.id ?? ''),
ip: String(v.ip ?? ''),
ipv6: String(v.ipv6 ?? ''),
additionalIps,
dns: String(v.dns ?? ''),
providerId: String(v.providerId ?? ''),
providerAccountId: String(v.providerAccountId ?? ''),
country: String(v.country ?? ''),
city: String(v.city ?? ''),
datacenter: String(v.datacenter ?? ''),
os: String(v.os ?? ''),
vcpu: Number(v.vcpu) || 0,
ramGb: Number(v.ramGb) || 0,
diskGb: Number(v.diskGb) || 0,
diskType: String(v.diskType ?? ''),
virtualization: String(v.virtualization ?? ''),
bandwidthTb: Number(v.bandwidthTb) || 0,
sshPort: Number(v.sshPort) || 22,
rootUser: String(v.rootUser ?? ''),
purpose: String(v.purpose ?? ''),
environment: String(v.environment ?? ''),
project: String(v.project ?? ''),
projectId: v.projectId != null && v.projectId !== '' ? String(v.projectId) : null,
monitoringEnabled: v.monitoringEnabled ? 1 : 0,
backupEnabled: v.backupEnabled ? 1 : 0,
status: String(v.status ?? 'active'),
tariffType: String(v.tariffType ?? ''),
currency: String(v.currency ?? ''),
dailyRate,
monthlyRate,
createdAt: String(v.createdAt ?? ''),
paidUntil: String(v.paidUntil ?? ''),
notes: String(v.notes ?? ''),
userOverrides,
})
.run()
}
for (const raw of Array.isArray(data.payments) ? data.payments : []) {
const pm = asObject(raw)
db.insert(schema.payments)
.values({
id: String(pm.id ?? ''),
type: String(pm.type ?? ''),
date: String(pm.date ?? ''),
amount: Number(pm.amount) || 0,
currency: String(pm.currency ?? ''),
providerAccountId: String(pm.providerAccountId ?? ''),
vpsId: pm.vpsId != null ? String(pm.vpsId) : null,
note: String(pm.note ?? ''),
})
.run()
}
for (const raw of Array.isArray(data.balanceLedger) ? data.balanceLedger : []) {
const bl = asObject(raw)
db.insert(schema.balanceLedger)
.values({
id: String(bl.id ?? ''),
type: String(bl.type ?? ''),
date: String(bl.date ?? ''),
amount: Number(bl.amount) || 0,
currency: String(bl.currency ?? ''),
direction: String(bl.direction ?? ''),
providerAccountId: String(bl.providerAccountId ?? ''),
vpsId: bl.vpsId != null ? String(bl.vpsId) : null,
note: String(bl.note ?? ''),
})
.run()
}
for (const raw of Array.isArray(data.activeTariffs) ? data.activeTariffs : []) {
const t = asObject(raw)
db.insert(schema.activeTariffs)
.values({
id: String(t.id ?? ''),
providerAccountId: String(t.providerAccountId ?? ''),
providerId: String(t.providerId ?? ''),
externalId: String(t.externalId ?? ''),
datacenterKey: String(t.datacenterKey ?? ''),
datacenterName: String(t.datacenterName ?? ''),
name: String(t.name ?? ''),
desc: String(t.desc ?? ''),
vcpu: Number(t.vcpu) || 0,
ramGb: Number(t.ramGb) || 0,
diskGb: Number(t.diskGb) || 0,
diskType: String(t.diskType ?? ''),
virtualization: String(t.virtualization ?? ''),
channel: String(t.channel ?? ''),
location: String(t.location ?? ''),
country: String(t.country ?? ''),
cpuModel: String(t.cpuModel ?? ''),
orderAvailable: t.orderAvailable ? 1 : 0,
price: String(t.price ?? ''),
syncedAt: String(t.syncedAt ?? ''),
})
.run()
}
for (const raw of Array.isArray(data.tariffSyncOptions) ? data.tariffSyncOptions : []) {
const o = asObject(raw)
const dcs = typeof o.datacenters === 'string' ? o.datacenters : JSON.stringify(o.datacenters || [])
const pers = typeof o.periods === 'string' ? o.periods : JSON.stringify(o.periods || [])
db.insert(schema.tariffSyncOptions)
.values({
providerAccountId: String(o.providerAccountId ?? ''),
datacenters: dcs,
periods: pers,
syncedAt: String(o.syncedAt ?? ''),
})
.run()
}
for (const raw of Array.isArray(data.syncLog) ? data.syncLog : []) {
const log = asObject(raw)
db.insert(schema.syncLog)
.values({
id: String(log.id ?? ''),
accountId: String(log.accountId ?? ''),
startedAt: String(log.startedAt ?? ''),
finishedAt: log.finishedAt != null ? String(log.finishedAt) : null,
status: log.status != null ? String(log.status) : null,
vpsCount: log.vpsCount != null ? Number(log.vpsCount) : null,
paymentsCount: log.paymentsCount != null ? Number(log.paymentsCount) : null,
error: log.error != null ? String(log.error) : null,
summary:
typeof log.summary === 'string'
? log.summary
: log.summary
? JSON.stringify(log.summary)
: null,
})
.run()
}
sqlite.exec('COMMIT')
} catch (err) {
sqlite.exec('ROLLBACK')
throw err
}
}