Files
DenozordecandCursor 3026f1d1ce fix(docker): копирование apps/api/db.js и routes/ в образ; пути к web/dist и seed
- Dockerfile: добавлены COPY для db.js, routes/, apps/web/public (seed)
- apps/api/index.js: distPath → ../web/dist (раньше ../dist)
- apps/api/db/index.js: DB_PATH/SEED_DIR/dataDir — на 3 уровня вверх
  (apps/api/db/ → корень) и seed из apps/web/public/data

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 13:51:19 +07:00

140 lines
3.4 KiB
JavaScript

/**
* Database initialization and access
*/
import initSqlJs from 'sql.js'
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import { SCHEMA } from './schema.js'
import { MIGRATIONS } from './migrations.js'
import { seed, isDbEmpty } from './seed.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
// db/ is in apps/api/, so .. = apps/api, ../.. = apps, ../../.. = project root
export const DB_PATH = join(__dirname, '..', '..', '..', 'data', 'vps-tracker.db')
const SEED_DIR = join(__dirname, '..', '..', '..', 'apps', 'web', 'public', 'data')
let dbInstance = null
/** @type {import('sql.js').SqlJsStatic | null} */
let sqlJsFactory = null
export async function initDb() {
const dataDir = join(__dirname, '..', '..', '..', 'data')
if (!existsSync(dataDir)) {
mkdirSync(dataDir, { recursive: true })
}
const SQL = await initSqlJs()
sqlJsFactory = SQL
let db
if (existsSync(DB_PATH)) {
const fileBuffer = readFileSync(DB_PATH)
db = new SQL.Database(fileBuffer)
} else {
db = new SQL.Database()
}
db.exec(SCHEMA)
for (const m of MIGRATIONS) {
try {
m.run(db)
} catch (err) {
console.warn(`Migration ${m.name} failed:`, err.message)
}
}
if (isDbEmpty(db)) {
seed(db, SEED_DIR)
const data = db.export()
writeFileSync(DB_PATH, Buffer.from(data))
}
dbInstance = db
if (existsSync(DB_PATH)) {
saveDb()
}
return db
}
export function getDb() {
if (!dbInstance) throw new Error('Database not initialized')
return createDbWrapper(dbInstance)
}
function createDbWrapper(db) {
return {
/** One-off run: prepare, bind, step, free. Use for statements that run once. */
run(sql, ...params) {
const stmt = db.prepare(sql)
stmt.bind(params)
stmt.step()
const changes = db.getRowsModified()
stmt.free()
saveDb()
return { changes }
},
prepare(sql) {
const stmt = db.prepare(sql)
return {
all(...params) {
stmt.bind(params)
const rows = []
while (stmt.step()) {
rows.push(stmt.getAsObject())
}
stmt.free()
return rows
},
get(...params) {
stmt.bind(params)
const row = stmt.step() ? stmt.getAsObject() : null
stmt.free()
return row
},
run(...params) {
stmt.bind(params)
stmt.step()
const changes = db.getRowsModified()
stmt.free()
saveDb()
return { changes }
},
}
},
}
}
export function saveDb() {
if (!dbInstance) return
const data = dbInstance.export()
writeFileSync(DB_PATH, Buffer.from(data))
}
/**
* Заменить in-memory БД из буфера SQLite и сохранить на диск.
* @param {Buffer|Uint8Array} buffer
*/
export async function reloadDatabaseFromBuffer(buffer) {
const SQL = sqlJsFactory || (await initSqlJs())
sqlJsFactory = SQL
if (dbInstance) {
dbInstance.close()
dbInstance = null
}
const u8 = buffer instanceof Buffer ? new Uint8Array(buffer) : buffer
dbInstance = new SQL.Database(u8)
dbInstance.exec(SCHEMA)
for (const m of MIGRATIONS) {
try {
m.run(dbInstance)
} catch (err) {
console.warn(`Migration ${m.name} after reload failed:`, err.message)
}
}
saveDb()
}