feat(api): завершение миграции на Fastify — sync, scheduler, backup
Docker / build (push) Has been cancelled
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:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user