feat: BILLmanager API на уровне хостера, миграция со старых аккаунтов
Docker / build (push) Failing after 18s
Docker / build (push) Failing after 18s
Made-with: Cursor
This commit is contained in:
@@ -2,6 +2,7 @@ import { Router } from 'express'
|
||||
import express from 'express'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { getDb, saveDb, DB_PATH, reloadDatabaseFromBuffer } from '../db.js'
|
||||
import { consolidateProviderApiFromAccounts } from '../db/migrations.js'
|
||||
import { rowToVps } from './vps.js'
|
||||
import { rowToActiveTariff, rowToTariffSyncOptions } from '../utils/row-mappers.js'
|
||||
|
||||
@@ -111,7 +112,7 @@ function importJsonSnapshot(data) {
|
||||
const providers = Array.isArray(data.providers) ? data.providers : []
|
||||
for (const p of providers) {
|
||||
run(
|
||||
`INSERT OR REPLACE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
`INSERT OR REPLACE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes, apiType, apiBaseUrl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
p.id ?? '',
|
||||
p.name ?? '',
|
||||
p.website ?? '',
|
||||
@@ -120,6 +121,8 @@ function importJsonSnapshot(data) {
|
||||
p.usdRate ?? '',
|
||||
p.eurRate ?? '',
|
||||
p.notes ?? '',
|
||||
p.apiType ?? '',
|
||||
p.apiBaseUrl ?? '',
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,6 +162,8 @@ function importJsonSnapshot(data) {
|
||||
)
|
||||
}
|
||||
|
||||
consolidateProviderApiFromAccounts(db)
|
||||
|
||||
const settingsList = Array.isArray(data.settings) ? data.settings : data.settings ? [data.settings] : []
|
||||
for (const s of settingsList) {
|
||||
let customFields = s.customFields
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb, saveDb } from '../db.js'
|
||||
import { consolidateProviderApiFromAccounts } from '../db/migrations.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -14,10 +15,22 @@ router.post('/', (req, res) => {
|
||||
const settingsList = Array.isArray(data.settings) ? data.settings : (data.settings ? [data.settings] : [])
|
||||
|
||||
if (Array.isArray(data.providers) && data.providers.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
const sql = `INSERT OR REPLACE INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes, apiType, apiBaseUrl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of data.providers) {
|
||||
const p = typeof r === 'object' ? r : {}
|
||||
db.run(sql, p.id ?? '', p.name ?? '', p.website ?? '', p.contact ?? '', p.baseCurrency ?? '', p.usdRate ?? '', p.eurRate ?? '', p.notes ?? '')
|
||||
db.run(
|
||||
sql,
|
||||
p.id ?? '',
|
||||
p.name ?? '',
|
||||
p.website ?? '',
|
||||
p.contact ?? '',
|
||||
p.baseCurrency ?? '',
|
||||
p.usdRate ?? '',
|
||||
p.eurRate ?? '',
|
||||
p.notes ?? '',
|
||||
p.apiType ?? '',
|
||||
p.apiBaseUrl ?? '',
|
||||
)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.providerAccounts) && data.providerAccounts.length > 0) {
|
||||
@@ -59,6 +72,7 @@ router.post('/', (req, res) => {
|
||||
db.run(sql, s.id ?? 'settings-main', s.baseCurrency ?? 'RUB', s.ratesUrl ?? '', s.autoConvert !== false ? 1 : 0, s.ratesUpdatedAt ?? '', s.syncEnabled ? 1 : 0, s.syncIntervalMinutes ?? 60)
|
||||
}
|
||||
}
|
||||
consolidateProviderApiFromAccounts(db)
|
||||
saveDb()
|
||||
|
||||
res.json({ ok: true })
|
||||
|
||||
@@ -30,7 +30,7 @@ router.post('/', (req, res) => {
|
||||
: null
|
||||
db.prepare(`
|
||||
INSERT INTO provider_accounts (id, providerId, name, panelUrl, currency, billingMode, notes, apiType, apiBaseUrl, apiCredentials, balance_alert_below)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, '', '', ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.providerId ?? '',
|
||||
@@ -39,8 +39,6 @@ router.post('/', (req, res) => {
|
||||
r.currency ?? '',
|
||||
r.billingMode ?? '',
|
||||
r.notes ?? '',
|
||||
r.apiType ?? '',
|
||||
r.apiBaseUrl ?? '',
|
||||
r.apiCredentials ?? '',
|
||||
Number.isFinite(alertBelow) ? alertBelow : null,
|
||||
)
|
||||
@@ -58,8 +56,6 @@ router.put('/:id', (req, res) => {
|
||||
const r = req.body
|
||||
const existing = db.prepare('SELECT * FROM provider_accounts WHERE id = ?').get(id)
|
||||
if (!existing) return res.status(404).json({ error: 'Not found' })
|
||||
const apiType = r.apiType !== undefined ? String(r.apiType || '') : (existing.apiType || '')
|
||||
const apiBaseUrl = r.apiBaseUrl !== undefined ? String(r.apiBaseUrl || '') : (existing.apiBaseUrl || '')
|
||||
const apiCredentials = r.apiCredentials !== undefined ? String(r.apiCredentials || '') : (existing.apiCredentials || '')
|
||||
let balanceAlertBelow = existing.balance_alert_below
|
||||
if (r.balance_alert_below !== undefined) {
|
||||
@@ -73,7 +69,8 @@ router.put('/:id', (req, res) => {
|
||||
}
|
||||
db.prepare(`
|
||||
UPDATE provider_accounts SET
|
||||
providerId = ?, name = ?, panelUrl = ?, currency = ?, billingMode = ?, notes = ?, apiType = ?, apiBaseUrl = ?, apiCredentials = ?, balance_alert_below = ?
|
||||
providerId = ?, name = ?, panelUrl = ?, currency = ?, billingMode = ?, notes = ?,
|
||||
apiType = '', apiBaseUrl = '', apiCredentials = ?, balance_alert_below = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.providerId ?? existing.providerId ?? '',
|
||||
@@ -82,8 +79,6 @@ router.put('/:id', (req, res) => {
|
||||
r.currency ?? existing.currency ?? '',
|
||||
r.billingMode ?? existing.billingMode ?? '',
|
||||
r.notes ?? existing.notes ?? '',
|
||||
apiType,
|
||||
apiBaseUrl,
|
||||
apiCredentials,
|
||||
balanceAlertBelow,
|
||||
id,
|
||||
|
||||
@@ -19,8 +19,8 @@ router.post('/', (req, res) => {
|
||||
const r = req.body
|
||||
const id = r.id || `provider-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||
db.prepare(`
|
||||
INSERT INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO providers (id, name, website, contact, baseCurrency, usdRate, eurRate, notes, apiType, apiBaseUrl)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.name ?? '',
|
||||
@@ -30,6 +30,8 @@ router.post('/', (req, res) => {
|
||||
r.usdRate ?? '',
|
||||
r.eurRate ?? '',
|
||||
r.notes ?? '',
|
||||
r.apiType ?? '',
|
||||
r.apiBaseUrl ?? '',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM providers WHERE id = ?').get(id)
|
||||
res.status(201).json(row)
|
||||
@@ -43,9 +45,15 @@ router.put('/:id', (req, res) => {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
const existing = db.prepare('SELECT * FROM providers WHERE id = ?').get(id)
|
||||
if (!existing) return res.status(404).json({ error: 'Not found' })
|
||||
const apiType = r.apiType !== undefined ? String(r.apiType || '') : (existing.apiType || '')
|
||||
const apiBaseUrl =
|
||||
r.apiBaseUrl !== undefined ? String(r.apiBaseUrl || '') : (existing.apiBaseUrl || '')
|
||||
db.prepare(`
|
||||
UPDATE providers SET
|
||||
name = ?, website = ?, contact = ?, baseCurrency = ?, usdRate = ?, eurRate = ?, notes = ?
|
||||
name = ?, website = ?, contact = ?, baseCurrency = ?, usdRate = ?, eurRate = ?, notes = ?,
|
||||
apiType = ?, apiBaseUrl = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.name ?? '',
|
||||
@@ -55,10 +63,11 @@ router.put('/:id', (req, res) => {
|
||||
r.usdRate ?? '',
|
||||
r.eurRate ?? '',
|
||||
r.notes ?? '',
|
||||
apiType,
|
||||
apiBaseUrl,
|
||||
id,
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM providers WHERE id = ?').get(id)
|
||||
if (!row) return res.status(404).json({ error: 'Not found' })
|
||||
res.json(row)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
|
||||
+23
-12
@@ -2,6 +2,7 @@ import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
import { fetchDashboardInfo, testConnection } from '../adapters/billmanager/index.js'
|
||||
import { runBillmanagerAccountSync } from '../sync-account-job.js'
|
||||
import { billmanagerAccountRowForSync } from '../utils/billmanager-context.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -51,13 +52,19 @@ router.get('/:accountId/balance', async (req, res) => {
|
||||
if (!row) {
|
||||
return res.status(404).json({ error: 'Account not found' })
|
||||
}
|
||||
if (row.apiType !== 'billmanager') {
|
||||
return res.status(400).json({ error: 'Account is not configured for BILLmanager API' })
|
||||
const provider = row.providerId
|
||||
? db.prepare('SELECT * FROM providers WHERE id = ?').get(row.providerId)
|
||||
: null
|
||||
const syncRow = billmanagerAccountRowForSync(row, provider)
|
||||
if (!syncRow) {
|
||||
return res.status(400).json({
|
||||
error:
|
||||
'Укажите в настройках хостера тип API BILLmanager и URL; в аккаунте — логин и пароль API',
|
||||
})
|
||||
}
|
||||
if (!row.apiBaseUrl?.trim() || !row.apiCredentials?.trim()) {
|
||||
return res.status(400).json({ error: 'API URL and credentials are required' })
|
||||
}
|
||||
const info = await fetchDashboardInfo(row.apiBaseUrl, row.apiCredentials.trim(), { fallbackCurrency: row.currency })
|
||||
const info = await fetchDashboardInfo(syncRow.apiBaseUrl, syncRow.apiCredentials.trim(), {
|
||||
fallbackCurrency: row.currency,
|
||||
})
|
||||
db.run(
|
||||
'UPDATE provider_accounts SET balance_api=?, balance_currency=?, balance_updated_at=?, enoughmoneyto=? WHERE id=?',
|
||||
info.balance,
|
||||
@@ -82,15 +89,19 @@ router.post('/:accountId', async (req, res) => {
|
||||
if (!row) {
|
||||
return res.status(404).json({ error: 'Account not found' })
|
||||
}
|
||||
if (row.apiType !== 'billmanager') {
|
||||
return res.status(400).json({ error: 'Account is not configured for BILLmanager API' })
|
||||
}
|
||||
if (!row.apiBaseUrl?.trim() || !row.apiCredentials?.trim()) {
|
||||
return res.status(400).json({ error: 'API URL and credentials are required' })
|
||||
const provider = row.providerId
|
||||
? db.prepare('SELECT * FROM providers WHERE id = ?').get(row.providerId)
|
||||
: null
|
||||
const syncRow = billmanagerAccountRowForSync(row, provider)
|
||||
if (!syncRow) {
|
||||
return res.status(400).json({
|
||||
error:
|
||||
'Укажите в настройках хостера тип API BILLmanager и URL; в аккаунте — логин и пароль API',
|
||||
})
|
||||
}
|
||||
|
||||
const opts = onlyTariffs ? { skipVpsPayments: true } : { skipTariffs: true }
|
||||
const result = await runBillmanagerAccountSync(row, opts)
|
||||
const result = await runBillmanagerAccountSync(syncRow, opts)
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user