feat(api): завершение миграции на Fastify — sync, scheduler, backup
Docker / build (push) Has been cancelled

Портированы BILLmanager sync/test/balance, планировщик и Telegram,
импорт/экспорт бэкапов и migrate API; Docker по умолчанию на fastify.
Добавлен .understand-anything/ в gitignore, исправлен path codegraph в MCP.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-06-27 23:22:59 +07:00
co-authored by Cursor
parent 836c8a2936
commit 0de0538218
24 changed files with 2192 additions and 46 deletions
+23 -2
View File
@@ -1,6 +1,6 @@
import Database from 'better-sqlite3'
import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import { existsSync, mkdirSync } from 'node:fs'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import * as schema from './schema/index.js'
@@ -17,6 +17,11 @@ export function getDbPath(): string {
export function getDb(): Db {
if (_db) return _db
openDatabase()
return _db!
}
function openDatabase(): void {
const dbPath = getDbPath()
const dir = dirname(dbPath)
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
@@ -24,7 +29,11 @@ export function getDb(): Db {
_sqlite.pragma('journal_mode = WAL')
_sqlite.pragma('foreign_keys = ON')
_db = drizzle(_sqlite, { schema })
return _db
}
export function getSqlite(): Database.Database {
getDb()
return _sqlite!
}
export function closeDb(): void {
@@ -35,4 +44,16 @@ export function closeDb(): void {
}
}
/** Заменить файл SQLite и переоткрыть соединение. */
export function reloadDatabaseFromBuffer(buffer: Buffer): void {
closeDb()
writeFileSync(getDbPath(), buffer)
openDatabase()
}
export { schema }
export {
consolidateAllProviderApiSources,
consolidateProviderApiFromAccounts,
heuristicBillmanagerProviderApi,
} from './maintenance/consolidate-api.js'
@@ -0,0 +1,83 @@
import type Database from 'better-sqlite3'
function tryBillmgrUrl(raw: unknown): string {
const t = String(raw || '').trim()
if (!t || !/^https?:\/\//i.test(t) || !/billmgr/i.test(t)) return ''
return t.replace(/\/+$/, '')
}
export function consolidateProviderApiFromAccounts(sqlite: Database.Database): void {
const provRows = sqlite.prepare('SELECT id, apiType, apiBaseUrl FROM providers').all() as {
id: string
apiType: string | null
apiBaseUrl: string | null
}[]
const accStmt = sqlite.prepare(
`SELECT apiBaseUrl FROM provider_accounts
WHERE providerId = ?
AND length(trim(COALESCE(apiBaseUrl, ''))) > 0
AND (
lower(trim(COALESCE(apiType, ''))) = 'billmanager'
OR instr(lower(trim(COALESCE(apiBaseUrl, ''))), 'billmgr') > 0
)
ORDER BY id`,
)
const updateProv = sqlite.prepare('UPDATE providers SET apiType = ?, apiBaseUrl = ? WHERE id = ?')
const clearAcc = sqlite.prepare(
`UPDATE provider_accounts SET apiType = '', apiBaseUrl = ''
WHERE providerId = ?
AND length(trim(COALESCE(apiBaseUrl, ''))) > 0
AND (
lower(trim(COALESCE(apiType, ''))) = 'billmanager'
OR instr(lower(trim(COALESCE(apiBaseUrl, ''))), 'billmgr') > 0
)`,
)
for (const prov of provRows) {
if (String(prov.apiType || '').trim() || String(prov.apiBaseUrl || '').trim()) continue
const accRows = accStmt.all(prov.id) as { apiBaseUrl: string | null }[]
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] У хостера ${prov.id} у нескольких аккаунтов разный URL BILLmanager — в настройках хостера взят первый.`,
)
}
updateProv.run('billmanager', urls[0], prov.id)
clearAcc.run(prov.id)
}
}
export function heuristicBillmanagerProviderApi(sqlite: Database.Database): void {
const provRows = sqlite.prepare('SELECT id, website, apiType, apiBaseUrl FROM providers').all() as {
id: string
website: string | null
apiType: string | null
apiBaseUrl: string | null
}[]
const accStmt = sqlite.prepare(
`SELECT panelUrl FROM provider_accounts
WHERE providerId = ? AND length(trim(COALESCE(panelUrl, ''))) > 0
ORDER BY id`,
)
const updateProv = sqlite.prepare('UPDATE providers SET apiType = ?, apiBaseUrl = ? WHERE id = ?')
for (const prov of provRows) {
if (String(prov.apiType || '').trim() || String(prov.apiBaseUrl || '').trim()) continue
let url = tryBillmgrUrl(prov.website)
if (!url) {
const accRows = accStmt.all(prov.id) as { panelUrl: string | null }[]
for (const row of accRows) {
url = tryBillmgrUrl(row.panelUrl)
if (url) break
}
}
if (url) updateProv.run('billmanager', url, prov.id)
}
}
export function consolidateAllProviderApiSources(sqlite: Database.Database): void {
consolidateProviderApiFromAccounts(sqlite)
heuristicBillmanagerProviderApi(sqlite)
}