albion/server/src/db/index.ts
Заид Омар Медхат 3b20b1e431 Phase 4: character profiles
Destiny-board profiles (mastery/spec/other-specs per crafting category),
premium flag, focus efficiency formula (250/30/30 per level), active
profile feeds tax + per-category focus costs into the ranker; editor UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 03:11:01 +05:00

105 lines
3.3 KiB
TypeScript

import Database from 'better-sqlite3';
import fs from 'node:fs';
import path from 'node:path';
import { DB_PATH } from '../config.js';
const SCHEMA = `
CREATE TABLE IF NOT EXISTS meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS items (
unique_name TEXT PRIMARY KEY, -- e.g. T4_BAG@1
base_name TEXT NOT NULL, -- e.g. T4_BAG
enchantment INTEGER NOT NULL DEFAULT 0,
tier INTEGER,
weight REAL,
item_value INTEGER,
shop_category TEXT,
shop_subcategory TEXT,
crafting_category TEXT,
name_en TEXT
);
CREATE INDEX IF NOT EXISTS idx_items_base ON items(base_name);
CREATE INDEX IF NOT EXISTS idx_items_name_en ON items(name_en);
CREATE TABLE IF NOT EXISTS recipes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
output_item TEXT NOT NULL,
variant INTEGER NOT NULL DEFAULT 0, -- alternative recipe index for the same output
amount_crafted REAL NOT NULL DEFAULT 1,
silver_cost REAL NOT NULL DEFAULT 0,
focus_cost REAL NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT 'crafting' -- 'crafting' | 'upgrade' (enchanting)
);
CREATE INDEX IF NOT EXISTS idx_recipes_output ON recipes(output_item);
CREATE TABLE IF NOT EXISTS recipe_inputs (
recipe_id INTEGER NOT NULL REFERENCES recipes(id) ON DELETE CASCADE,
input_item TEXT NOT NULL,
count REAL NOT NULL,
max_return_amount REAL, -- 0 means the resource is never returned (e.g. artifacts)
PRIMARY KEY (recipe_id, input_item)
);
CREATE INDEX IF NOT EXISTS idx_recipe_inputs_item ON recipe_inputs(input_item);
CREATE TABLE IF NOT EXISTS market_prices (
item TEXT NOT NULL,
city TEXT NOT NULL,
quality INTEGER NOT NULL DEFAULT 1,
sell_price_min REAL,
sell_min_date TEXT, -- when AODP last observed this sell order (UTC ISO)
buy_price_max REAL,
buy_max_date TEXT,
fetched_at TEXT NOT NULL,
PRIMARY KEY (item, city, quality)
);
CREATE INDEX IF NOT EXISTS idx_market_prices_city ON market_prices(city);
CREATE TABLE IF NOT EXISTS profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 0,
premium INTEGER NOT NULL DEFAULT 1,
data TEXT NOT NULL DEFAULT '{}', -- JSON: destiny board levels per crafting category
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS price_overrides (
item TEXT NOT NULL,
city TEXT NOT NULL,
quality INTEGER NOT NULL DEFAULT 1,
sell_price REAL,
buy_price REAL,
note TEXT,
updated_at TEXT NOT NULL,
PRIMARY KEY (item, city, quality)
);
`;
let db: Database.Database | null = null;
export function getDb(): Database.Database {
if (!db) {
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
db.exec(SCHEMA);
}
return db;
}
export function getMeta(key: string): string | null {
const row = getDb().prepare('SELECT value FROM meta WHERE key = ?').get(key) as
| { value: string }
| undefined;
return row?.value ?? null;
}
export function setMeta(key: string, value: string): void {
getDb()
.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')
.run(key, value);
}