init commit
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM balance_ledger ORDER BY date DESC').all()
|
||||
res.json(rows)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const r = req.body
|
||||
const id = r.id || `ledger-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||
db.prepare(`
|
||||
INSERT INTO balance_ledger (id, type, date, amount, currency, direction, providerAccountId, vpsId, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.type ?? '',
|
||||
r.date ?? '',
|
||||
Number(r.amount) || 0,
|
||||
r.currency ?? '',
|
||||
r.direction ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.vpsId ?? '',
|
||||
r.note ?? '',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM balance_ledger WHERE id = ?').get(id)
|
||||
res.status(201).json(row)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
db.prepare(`
|
||||
UPDATE balance_ledger SET
|
||||
type = ?, date = ?, amount = ?, currency = ?, direction = ?, providerAccountId = ?, vpsId = ?, note = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.type ?? '',
|
||||
r.date ?? '',
|
||||
Number(r.amount) || 0,
|
||||
r.currency ?? '',
|
||||
r.direction ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.vpsId ?? '',
|
||||
r.note ?? '',
|
||||
id,
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM balance_ledger 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 })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const result = db.prepare('DELETE FROM balance_ledger WHERE id = ?').run(id)
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
||||
res.status(204).send()
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
import { rowToVps } from './vps.js'
|
||||
import { rowToSettings } from './settings.js'
|
||||
import { sanitizeAccount, rowToActiveTariff, rowToTariffSyncOptions } from '../utils/row-mappers.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const vps = db.prepare('SELECT * FROM vps ORDER BY createdAt DESC').all()
|
||||
const providers = db.prepare('SELECT * FROM providers ORDER BY name').all()
|
||||
const providerAccounts = db.prepare('SELECT * FROM provider_accounts ORDER BY name').all()
|
||||
const payments = db.prepare('SELECT * FROM payments ORDER BY date DESC').all()
|
||||
const balanceLedger = db.prepare('SELECT * FROM balance_ledger ORDER BY date DESC').all()
|
||||
const settingsRows = db.prepare('SELECT * FROM settings ORDER BY id').all()
|
||||
const activeTariffs = db.prepare('SELECT * FROM active_tariffs ORDER BY name').all()
|
||||
const tariffSyncOptions = db.prepare('SELECT * FROM tariff_sync_options').all()
|
||||
|
||||
res.json({
|
||||
vps: vps.map(rowToVps),
|
||||
providers,
|
||||
providerAccounts: providerAccounts.map(sanitizeAccount),
|
||||
payments,
|
||||
balanceLedger,
|
||||
settings: settingsRows.map(rowToSettings),
|
||||
activeTariffs: activeTariffs.map(rowToActiveTariff),
|
||||
tariffSyncOptions: tariffSyncOptions.map(rowToTariffSyncOptions),
|
||||
})
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb, saveDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const data = req.body
|
||||
if (!data || typeof data !== 'object') {
|
||||
return res.status(400).json({ error: 'Invalid payload' })
|
||||
}
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
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 ?? '')
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.providerAccounts) && data.providerAccounts.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO provider_accounts (id, providerId, name, panelUrl, currency, billingMode, notes, apiType, apiBaseUrl, apiCredentials) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of data.providerAccounts) {
|
||||
const acc = typeof r === 'object' ? r : {}
|
||||
db.run(sql, acc.id ?? '', acc.providerId ?? '', acc.name ?? '', acc.panelUrl ?? '', acc.currency ?? '', acc.billingMode ?? '', acc.notes ?? '', acc.apiType ?? '', acc.apiBaseUrl ?? '', acc.apiCredentials ?? '')
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.vps) && data.vps.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO vps (id, ip, ipv6, additionalIps, dns, providerId, providerAccountId, country, city, datacenter, os, vcpu, ramGb, diskGb, diskType, virtualization, bandwidthTb, sshPort, rootUser, purpose, environment, project, monitoringEnabled, backupEnabled, status, tariffType, currency, dailyRate, monthlyRate, createdAt, paidUntil, notes, userOverrides) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of data.vps) {
|
||||
const v = typeof r === 'object' ? r : {}
|
||||
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) : (v.userOverrides ?? '')
|
||||
db.run(sql, v.id ?? '', v.ip ?? '', v.ipv6 ?? '', additionalIps, v.dns ?? '', v.providerId ?? '', v.providerAccountId ?? '', v.country ?? '', v.city ?? '', v.datacenter ?? '', v.os ?? '', v.vcpu ?? 0, v.ramGb ?? 0, v.diskGb ?? 0, v.diskType ?? '', v.virtualization ?? '', v.bandwidthTb ?? 0, v.sshPort ?? 22, v.rootUser ?? '', v.purpose ?? '', v.environment ?? '', v.project ?? '', v.monitoringEnabled ? 1 : 0, v.backupEnabled ? 1 : 0, v.status ?? 'active', v.tariffType ?? '', v.currency ?? '', dailyRate, monthlyRate, v.createdAt ?? '', v.paidUntil ?? '', v.notes ?? '', userOverrides)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.payments) && data.payments.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO payments (id, type, date, amount, currency, providerAccountId, vpsId, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of data.payments) {
|
||||
const pm = typeof r === 'object' ? r : {}
|
||||
db.run(sql, pm.id ?? '', pm.type ?? '', pm.date ?? '', Number(pm.amount) || 0, pm.currency ?? '', pm.providerAccountId ?? '', pm.vpsId ?? '', pm.note ?? '')
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.balanceLedger) && data.balanceLedger.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO balance_ledger (id, type, date, amount, currency, direction, providerAccountId, vpsId, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of data.balanceLedger) {
|
||||
const bl = typeof r === 'object' ? r : {}
|
||||
db.run(sql, bl.id ?? '', bl.type ?? '', bl.date ?? '', Number(bl.amount) || 0, bl.currency ?? '', bl.direction ?? '', bl.providerAccountId ?? '', bl.vpsId ?? '', bl.note ?? '')
|
||||
}
|
||||
}
|
||||
if (settingsList.length > 0) {
|
||||
const sql = `INSERT OR REPLACE INTO settings (id, baseCurrency, ratesUrl, autoConvert, ratesUpdatedAt, syncEnabled, syncIntervalMinutes) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
||||
for (const r of settingsList) {
|
||||
const s = typeof r === 'object' ? r : {}
|
||||
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)
|
||||
}
|
||||
}
|
||||
saveDb()
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (err) {
|
||||
console.error('Migrate error:', err)
|
||||
res.status(500).json({ error: err.message || 'Migration failed' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM payments ORDER BY date DESC').all()
|
||||
res.json(rows)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const r = req.body
|
||||
const id = r.id || `pay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||
db.prepare(`
|
||||
INSERT INTO payments (id, type, date, amount, currency, providerAccountId, vpsId, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.type ?? '',
|
||||
r.date ?? '',
|
||||
Number(r.amount) || 0,
|
||||
r.currency ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.vpsId ?? '',
|
||||
r.note ?? '',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM payments WHERE id = ?').get(id)
|
||||
res.status(201).json(row)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
db.prepare(`
|
||||
UPDATE payments SET
|
||||
type = ?, date = ?, amount = ?, currency = ?, providerAccountId = ?, vpsId = ?, note = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.type ?? '',
|
||||
r.date ?? '',
|
||||
Number(r.amount) || 0,
|
||||
r.currency ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.vpsId ?? '',
|
||||
r.note ?? '',
|
||||
id,
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM payments 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 })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const result = db.prepare('DELETE FROM payments WHERE id = ?').run(id)
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
||||
res.status(204).send()
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
function sanitizeAccount(row) {
|
||||
if (!row) return row
|
||||
const { apiCredentials, ...rest } = row
|
||||
return { ...rest, apiCredentialsSet: Boolean(apiCredentials) }
|
||||
}
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM provider_accounts ORDER BY name').all()
|
||||
res.json(rows.map(sanitizeAccount))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const r = req.body
|
||||
const id = r.id || `account-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||
db.prepare(`
|
||||
INSERT INTO provider_accounts (id, providerId, name, panelUrl, currency, billingMode, notes, apiType, apiBaseUrl, apiCredentials)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.providerId ?? '',
|
||||
r.name ?? '',
|
||||
r.panelUrl ?? '',
|
||||
r.currency ?? '',
|
||||
r.billingMode ?? '',
|
||||
r.notes ?? '',
|
||||
r.apiType ?? '',
|
||||
r.apiBaseUrl ?? '',
|
||||
r.apiCredentials ?? '',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM provider_accounts WHERE id = ?').get(id)
|
||||
res.status(201).json(sanitizeAccount(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
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 || '')
|
||||
db.prepare(`
|
||||
UPDATE provider_accounts SET
|
||||
providerId = ?, name = ?, panelUrl = ?, currency = ?, billingMode = ?, notes = ?, apiType = ?, apiBaseUrl = ?, apiCredentials = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.providerId ?? existing.providerId ?? '',
|
||||
r.name ?? existing.name ?? '',
|
||||
r.panelUrl ?? existing.panelUrl ?? '',
|
||||
r.currency ?? existing.currency ?? '',
|
||||
r.billingMode ?? existing.billingMode ?? '',
|
||||
r.notes ?? existing.notes ?? '',
|
||||
apiType,
|
||||
apiBaseUrl,
|
||||
apiCredentials,
|
||||
id,
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM provider_accounts WHERE id = ?').get(id)
|
||||
res.json(sanitizeAccount(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const result = db.prepare('DELETE FROM provider_accounts WHERE id = ?').run(id)
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
||||
res.status(204).send()
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM providers ORDER BY name').all()
|
||||
res.json(rows)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.name ?? '',
|
||||
r.website ?? '',
|
||||
r.contact ?? '',
|
||||
r.baseCurrency ?? '',
|
||||
r.usdRate ?? '',
|
||||
r.eurRate ?? '',
|
||||
r.notes ?? '',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM providers WHERE id = ?').get(id)
|
||||
res.status(201).json(row)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
db.prepare(`
|
||||
UPDATE providers SET
|
||||
name = ?, website = ?, contact = ?, baseCurrency = ?, usdRate = ?, eurRate = ?, notes = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.name ?? '',
|
||||
r.website ?? '',
|
||||
r.contact ?? '',
|
||||
r.baseCurrency ?? '',
|
||||
r.usdRate ?? '',
|
||||
r.eurRate ?? '',
|
||||
r.notes ?? '',
|
||||
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 })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const result = db.prepare('DELETE FROM providers WHERE id = ?').run(id)
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
||||
res.status(204).send()
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
import { startScheduler } from '../sync-scheduler.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
export function rowToSettings(row) {
|
||||
if (!row) return null
|
||||
return {
|
||||
...row,
|
||||
autoConvert: Boolean(row.autoConvert),
|
||||
syncEnabled: Boolean(row.syncEnabled),
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM settings ORDER BY id').all()
|
||||
res.json(rows.map(rowToSettings))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
const existing = db.prepare('SELECT * FROM settings WHERE id = ?').get(id)
|
||||
const syncEnabled = r.syncEnabled !== undefined ? (r.syncEnabled ? 1 : 0) : (existing?.syncEnabled ? 1 : 0)
|
||||
const syncIntervalMinutes = r.syncIntervalMinutes !== undefined ? Math.max(15, Number(r.syncIntervalMinutes) || 60) : (existing?.syncIntervalMinutes ?? 60)
|
||||
if (existing) {
|
||||
db.prepare(`
|
||||
UPDATE settings SET
|
||||
baseCurrency = ?, ratesUrl = ?, autoConvert = ?, ratesUpdatedAt = ?, syncEnabled = ?, syncIntervalMinutes = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.baseCurrency ?? existing.baseCurrency ?? 'RUB',
|
||||
r.ratesUrl ?? existing.ratesUrl ?? '',
|
||||
r.autoConvert !== undefined ? (r.autoConvert !== false ? 1 : 0) : (existing.autoConvert ? 1 : 0),
|
||||
r.ratesUpdatedAt ?? existing.ratesUpdatedAt ?? '',
|
||||
syncEnabled,
|
||||
syncIntervalMinutes,
|
||||
id,
|
||||
)
|
||||
} else {
|
||||
db.prepare(`
|
||||
INSERT INTO settings (id, baseCurrency, ratesUrl, autoConvert, ratesUpdatedAt, syncEnabled, syncIntervalMinutes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.baseCurrency ?? 'RUB',
|
||||
r.ratesUrl ?? '',
|
||||
r.autoConvert !== false ? 1 : 0,
|
||||
r.ratesUpdatedAt ?? '',
|
||||
syncEnabled,
|
||||
syncIntervalMinutes,
|
||||
)
|
||||
}
|
||||
startScheduler()
|
||||
const row = db.prepare('SELECT * FROM settings WHERE id = ?').get(id)
|
||||
res.json(rowToSettings(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const r = req.body
|
||||
const id = r.id ?? 'settings-main'
|
||||
const syncEnabled = r.syncEnabled ? 1 : 0
|
||||
const syncIntervalMinutes = Math.max(15, Number(r.syncIntervalMinutes) || 60)
|
||||
db.prepare(`
|
||||
INSERT INTO settings (id, baseCurrency, ratesUrl, autoConvert, ratesUpdatedAt, syncEnabled, syncIntervalMinutes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.baseCurrency ?? 'RUB',
|
||||
r.ratesUrl ?? 'https://www.cbr-xml-daily.ru/latest.js',
|
||||
r.autoConvert !== false ? 1 : 0,
|
||||
r.ratesUpdatedAt ?? '',
|
||||
syncEnabled,
|
||||
syncIntervalMinutes,
|
||||
)
|
||||
startScheduler()
|
||||
const row = db.prepare('SELECT * FROM settings WHERE id = ?').get(id)
|
||||
res.status(201).json(rowToSettings(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
import { syncFromBillmanager, fetchDashboardInfo, testConnection } from '../adapters/billmanager/index.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.post('/test-connection', async (req, res) => {
|
||||
try {
|
||||
const { apiBaseUrl, apiCredentials } = req.body || {}
|
||||
if (!apiBaseUrl?.trim() || !apiCredentials?.trim()) {
|
||||
return res.status(400).json({ ok: false, error: 'Укажите URL и учётные данные' })
|
||||
}
|
||||
const result = await testConnection(apiBaseUrl.trim(), apiCredentials.trim())
|
||||
res.json(result)
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message || 'Ошибка проверки' })
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare(`
|
||||
SELECT accountId, startedAt, finishedAt, status, vpsCount, paymentsCount, error
|
||||
FROM sync_log ORDER BY startedAt DESC LIMIT 50
|
||||
`).all()
|
||||
res.json(rows)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/:accountId/balance', async (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { accountId } = req.params
|
||||
const row = db.prepare('SELECT * FROM provider_accounts WHERE id = ?').get(accountId)
|
||||
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 info = await fetchDashboardInfo(row.apiBaseUrl, row.apiCredentials.trim(), { fallbackCurrency: row.currency })
|
||||
db.run(
|
||||
'UPDATE provider_accounts SET balance_api=?, balance_currency=?, balance_updated_at=?, enoughmoneyto=? WHERE id=?',
|
||||
info.balance,
|
||||
info.currency || 'RUB',
|
||||
new Date().toISOString(),
|
||||
info.enoughmoneyto || '',
|
||||
accountId,
|
||||
)
|
||||
res.json({ ok: true, balance: info })
|
||||
} catch (err) {
|
||||
console.error('Balance fetch error:', err)
|
||||
res.status(500).json({ ok: false, error: err.message || 'Failed to fetch balance' })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/:accountId', async (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { accountId } = req.params
|
||||
const row = db.prepare('SELECT * FROM provider_accounts WHERE id = ?').get(accountId)
|
||||
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 logId = `sync-${accountId}-${Date.now()}`
|
||||
db.prepare(`
|
||||
INSERT INTO sync_log (id, accountId, startedAt, status)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(logId, accountId, new Date().toISOString(), 'running')
|
||||
|
||||
const result = await syncFromBillmanager(row, db)
|
||||
|
||||
db.prepare(`
|
||||
UPDATE sync_log SET finishedAt=?, status=?, vpsCount=?, paymentsCount=?
|
||||
WHERE id=?
|
||||
`).run(new Date().toISOString(), 'ok', result.vpsCount, result.paymentsCount, logId)
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
synced: {
|
||||
vpsCount: result.vpsCount,
|
||||
paymentsCount: result.paymentsCount,
|
||||
tariffsCount: result.tariffsCount ?? 0,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Sync error:', err)
|
||||
const { accountId } = req.params
|
||||
const db = getDb()
|
||||
const logRows = db.prepare('SELECT id FROM sync_log WHERE accountId=? AND status=? ORDER BY startedAt DESC LIMIT 1').all(accountId, 'running')
|
||||
if (logRows.length > 0) {
|
||||
db.prepare('UPDATE sync_log SET finishedAt=?, status=?, error=? WHERE id=?').run(
|
||||
new Date().toISOString(),
|
||||
'error',
|
||||
err.message || 'Unknown error',
|
||||
logRows[0].id,
|
||||
)
|
||||
}
|
||||
res.status(500).json({ ok: false, error: err.message || 'Sync failed' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,197 @@
|
||||
import { Router } from 'express'
|
||||
import { getDb } from '../db.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
export function rowToVps(row) {
|
||||
if (!row) return null
|
||||
let additionalIps = []
|
||||
try {
|
||||
additionalIps = row.additionalIps ? JSON.parse(row.additionalIps) : []
|
||||
} catch {
|
||||
additionalIps = []
|
||||
}
|
||||
let userOverrides = []
|
||||
try {
|
||||
userOverrides = row.userOverrides ? JSON.parse(row.userOverrides) : []
|
||||
} catch {
|
||||
userOverrides = []
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
additionalIps,
|
||||
userOverrides,
|
||||
monitoringEnabled: Boolean(row.monitoringEnabled),
|
||||
backupEnabled: Boolean(row.backupEnabled),
|
||||
dailyRate: row.dailyRate != null ? row.dailyRate : '',
|
||||
monthlyRate: row.monthlyRate != null ? row.monthlyRate : '',
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const rows = db.prepare('SELECT * FROM vps ORDER BY createdAt DESC').all()
|
||||
res.json(rows.map(rowToVps))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const r = req.body
|
||||
const id = r.id || `vps-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||
const additionalIps = Array.isArray(r.additionalIps) ? JSON.stringify(r.additionalIps) : '[]'
|
||||
const dailyRate = r.dailyRate === '' || r.dailyRate == null ? null : Number(r.dailyRate)
|
||||
const monthlyRate = r.monthlyRate === '' || r.monthlyRate == null ? null : Number(r.monthlyRate)
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO vps (
|
||||
id, ip, ipv6, additionalIps, dns, providerId, providerAccountId, country, city, datacenter,
|
||||
os, vcpu, ramGb, diskGb, diskType, virtualization, bandwidthTb, sshPort, rootUser,
|
||||
purpose, environment, project, monitoringEnabled, backupEnabled, status, tariffType,
|
||||
currency, dailyRate, monthlyRate, createdAt, paidUntil, notes, userOverrides
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
r.ip ?? '',
|
||||
r.ipv6 ?? '',
|
||||
additionalIps,
|
||||
r.dns ?? '',
|
||||
r.providerId ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.country ?? '',
|
||||
r.city ?? '',
|
||||
r.datacenter ?? '',
|
||||
r.os ?? '',
|
||||
r.vcpu ?? 0,
|
||||
r.ramGb ?? 0,
|
||||
r.diskGb ?? 0,
|
||||
r.diskType ?? '',
|
||||
r.virtualization ?? '',
|
||||
r.bandwidthTb ?? 0,
|
||||
r.sshPort ?? 22,
|
||||
r.rootUser ?? '',
|
||||
r.purpose ?? '',
|
||||
r.environment ?? '',
|
||||
r.project ?? '',
|
||||
r.monitoringEnabled ? 1 : 0,
|
||||
r.backupEnabled ? 1 : 0,
|
||||
r.status ?? 'active',
|
||||
r.tariffType ?? '',
|
||||
r.currency ?? '',
|
||||
dailyRate,
|
||||
monthlyRate,
|
||||
r.createdAt ?? new Date().toISOString().slice(0, 10),
|
||||
r.paidUntil ?? '',
|
||||
r.notes ?? '',
|
||||
r.userOverrides ? (Array.isArray(r.userOverrides) ? JSON.stringify(r.userOverrides) : r.userOverrides) : '[]',
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
||||
res.status(201).json(rowToVps(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
const USER_OVERRIDABLE_FIELDS = ['country', 'city', 'datacenter', 'os', 'vcpu', 'ramGb', 'diskGb', 'diskType', 'virtualization', 'purpose', 'environment', 'project', 'notes', 'sshPort', 'rootUser', 'bandwidthTb', 'monitoringEnabled', 'backupEnabled']
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const r = req.body
|
||||
const existing = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
||||
if (!existing) return res.status(404).json({ error: 'Not found' })
|
||||
|
||||
let userOverrides = []
|
||||
try {
|
||||
userOverrides = existing.userOverrides ? JSON.parse(existing.userOverrides) : []
|
||||
} catch {
|
||||
userOverrides = []
|
||||
}
|
||||
if (r.userOverrides === 'clear' || (Array.isArray(r.userOverrides) && r.userOverrides.length === 0)) {
|
||||
userOverrides = []
|
||||
} else {
|
||||
for (const f of USER_OVERRIDABLE_FIELDS) {
|
||||
const newVal = r[f]
|
||||
const oldVal = existing[f]
|
||||
const changed = String(newVal ?? '') !== String(oldVal ?? '')
|
||||
if (changed && !userOverrides.includes(f)) {
|
||||
userOverrides.push(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
const userOverridesJson = JSON.stringify([...new Set(userOverrides)])
|
||||
|
||||
const additionalIps = Array.isArray(r.additionalIps) ? JSON.stringify(r.additionalIps) : '[]'
|
||||
const dailyRate = r.dailyRate === '' || r.dailyRate == null ? null : Number(r.dailyRate)
|
||||
const monthlyRate = r.monthlyRate === '' || r.monthlyRate == null ? null : Number(r.monthlyRate)
|
||||
|
||||
db.prepare(`
|
||||
UPDATE vps SET
|
||||
ip = ?, ipv6 = ?, additionalIps = ?, dns = ?, providerId = ?, providerAccountId = ?,
|
||||
country = ?, city = ?, datacenter = ?, os = ?, vcpu = ?, ramGb = ?, diskGb = ?, diskType = ?,
|
||||
virtualization = ?, bandwidthTb = ?, sshPort = ?, rootUser = ?, purpose = ?, environment = ?,
|
||||
project = ?, monitoringEnabled = ?, backupEnabled = ?, status = ?, tariffType = ?,
|
||||
currency = ?, dailyRate = ?, monthlyRate = ?, createdAt = ?, paidUntil = ?, notes = ?,
|
||||
userOverrides = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
r.ip ?? '',
|
||||
r.ipv6 ?? '',
|
||||
additionalIps,
|
||||
r.dns ?? '',
|
||||
r.providerId ?? '',
|
||||
r.providerAccountId ?? '',
|
||||
r.country ?? '',
|
||||
r.city ?? '',
|
||||
r.datacenter ?? '',
|
||||
r.os ?? '',
|
||||
r.vcpu ?? 0,
|
||||
r.ramGb ?? 0,
|
||||
r.diskGb ?? 0,
|
||||
r.diskType ?? '',
|
||||
r.virtualization ?? '',
|
||||
r.bandwidthTb ?? 0,
|
||||
r.sshPort ?? 22,
|
||||
r.rootUser ?? '',
|
||||
r.purpose ?? '',
|
||||
r.environment ?? '',
|
||||
r.project ?? '',
|
||||
r.monitoringEnabled ? 1 : 0,
|
||||
r.backupEnabled ? 1 : 0,
|
||||
r.status ?? 'active',
|
||||
r.tariffType ?? '',
|
||||
r.currency ?? '',
|
||||
dailyRate,
|
||||
monthlyRate,
|
||||
r.createdAt ?? '',
|
||||
r.paidUntil ?? '',
|
||||
r.notes ?? '',
|
||||
userOverridesJson,
|
||||
id,
|
||||
)
|
||||
const row = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
||||
if (!row) return res.status(404).json({ error: 'Not found' })
|
||||
res.json(rowToVps(row))
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDb()
|
||||
const { id } = req.params
|
||||
const result = db.prepare('DELETE FROM vps WHERE id = ?').run(id)
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
||||
res.status(204).send()
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user