feat: BILLmanager API на уровне хостера, миграция со старых аккаунтов
Docker / build (push) Failing after 18s

Made-with: Cursor
This commit is contained in:
Denozordec
2026-03-21 00:52:49 +07:00
parent 2da1f6a6f0
commit 28d723983e
19 changed files with 445 additions and 97 deletions
+70
View File
@@ -4,6 +4,60 @@
import { randomUUID } from 'node:crypto'
/**
* Перенос apiType/apiBaseUrl с аккаунтов на хостера (idempotent).
* Вызывается из миграции и после импорта бэкапа / старого migrate API.
* @param {import('sql.js').Database} db
*/
function selectAllObjects(db, sql, params = []) {
const prepared = db.prepare(sql)
if (typeof prepared.all === 'function') {
return prepared.all(...params)
}
const stmt = prepared
stmt.bind(params)
const rows = []
while (stmt.step()) {
rows.push(stmt.getAsObject())
}
stmt.free()
return rows
}
export function consolidateProviderApiFromAccounts(db) {
const provRows = selectAllObjects(db, 'SELECT id, apiType, apiBaseUrl FROM providers')
for (const prov of provRows) {
const pid = prov.id
if (String(prov.apiType || '').trim() || String(prov.apiBaseUrl || '').trim()) {
continue
}
const accRows = selectAllObjects(
db,
`SELECT apiBaseUrl FROM provider_accounts
WHERE providerId = ?
AND lower(trim(COALESCE(apiType, ''))) = 'billmanager'
AND length(trim(COALESCE(apiBaseUrl, ''))) > 0
ORDER BY id`,
[pid],
)
if (!accRows.length) continue
const urls = [...new Set(accRows.map((r) => String(r.apiBaseUrl || '').trim()).filter(Boolean))]
if (urls.length > 1) {
console.warn(
`[vps-tracker] У хостера ${pid} у нескольких аккаунтов разный URL BILLmanager — в настройках хостера взят первый.`,
)
}
const apiBaseUrl = urls[0]
db.run(`UPDATE providers SET apiType = ?, apiBaseUrl = ? WHERE id = ?`, 'billmanager', apiBaseUrl, pid)
db.run(
`UPDATE provider_accounts SET apiType = '', apiBaseUrl = ''
WHERE providerId = ? AND lower(trim(COALESCE(apiType, ''))) = 'billmanager'
AND length(trim(COALESCE(apiBaseUrl, ''))) > 0`,
pid,
)
}
}
export const MIGRATIONS = [
{
name: 'provider_accounts_api',
@@ -266,4 +320,20 @@ export const MIGRATIONS = [
}
},
},
{
name: 'providers_api_integration',
run(db) {
try {
db.exec('ALTER TABLE providers ADD COLUMN apiType TEXT')
} catch (e) {
if (!String(e.message || e).includes('duplicate column')) throw e
}
try {
db.exec('ALTER TABLE providers ADD COLUMN apiBaseUrl TEXT')
} catch (e) {
if (!String(e.message || e).includes('duplicate column')) throw e
}
consolidateProviderApiFromAccounts(db)
},
},
]
+3 -1
View File
@@ -11,7 +11,9 @@ CREATE TABLE IF NOT EXISTS providers (
baseCurrency TEXT,
usdRate TEXT,
eurRate TEXT,
notes TEXT
notes TEXT,
apiType TEXT,
apiBaseUrl TEXT
);
CREATE TABLE IF NOT EXISTS provider_accounts (
+23 -3
View File
@@ -31,17 +31,37 @@ export function seed(db, seedDir) {
const run = db.run.bind(db)
for (const r of providers) {
run(
'INSERT OR IGNORE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[r.id, r.name ?? '', r.website ?? '', r.contact ?? '', r.baseCurrency ?? '', r.usdRate ?? '', r.eurRate ?? '', r.notes ?? ''],
'INSERT OR IGNORE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes, apiType, apiBaseUrl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
r.id,
r.name ?? '',
r.website ?? '',
r.contact ?? '',
r.baseCurrency ?? '',
r.usdRate ?? '',
r.eurRate ?? '',
r.notes ?? '',
r.apiType ?? '',
r.apiBaseUrl ?? '',
],
)
}
const providerAccounts = loadJson(join(seedDir, 'provider-accounts.json'))
for (const r of providerAccounts) {
const legacyType = r.apiType ?? ''
const legacyUrl = r.apiBaseUrl ?? ''
run(
'INSERT OR IGNORE INTO provider_accounts (id, providerId, name, panelUrl, currency, billingMode, notes, apiType, apiBaseUrl, apiCredentials) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[r.id, r.providerId ?? '', r.name ?? '', r.panelUrl ?? '', r.currency ?? '', r.billingMode ?? '', r.notes ?? '', r.apiType ?? '', r.apiBaseUrl ?? '', r.apiCredentials ?? ''],
[r.id, r.providerId ?? '', r.name ?? '', r.panelUrl ?? '', r.currency ?? '', r.billingMode ?? '', r.notes ?? '', '', '', r.apiCredentials ?? ''],
)
if (legacyType === 'billmanager' && String(legacyUrl).trim()) {
run(
`UPDATE providers SET apiType = 'billmanager', apiBaseUrl = ? WHERE id = ? AND length(trim(COALESCE(apiBaseUrl,''))) = 0`,
String(legacyUrl).trim(),
r.providerId ?? '',
)
}
}
const vpsList = loadJson(join(seedDir, 'vps.json'))