diff --git a/backend/src/lib/db.ts b/backend/src/lib/db.ts new file mode 100644 index 0000000..f2542f3 --- /dev/null +++ b/backend/src/lib/db.ts @@ -0,0 +1,212 @@ +import Database from 'better-sqlite3'; +import path from 'path'; +import { seedDatabase } from './seed'; + +const DB_PATH = process.env.DB_PATH || path.join(process.cwd(), 'data', 'zombie_invasion.db'); + +let db: Database.Database; + +export function getDb(): Database.Database { + if (!db) { + const fs = require('fs'); + const dir = path.dirname(DB_PATH); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + db = new Database(DB_PATH); + db.pragma('journal_mode = WAL'); + db.pragma('foreign_keys = ON'); + initSchema(db); + seedDatabase(); + } + return db; +} + +function initSchema(db: Database.Database) { + db.exec(` + CREATE TABLE IF NOT EXISTS players ( + steam_id TEXT PRIMARY KEY, + player_name TEXT NOT NULL, + profile_level INTEGER DEFAULT 1, + free_currency INTEGER DEFAULT 0, + donate_currency INTEGER DEFAULT 0, + dust_currency INTEGER DEFAULT 0, + arcade_pack_credits TEXT DEFAULT '{"standard":0,"premium":0}', + sounds_wheel TEXT DEFAULT '{}', + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS game_sessions ( + game_id TEXT PRIMARY KEY, + match_id INTEGER, + session_id TEXT, + status TEXT DEFAULT 'active', + created_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS game_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + steam_id TEXT NOT NULL, + game_id TEXT, + match_id INTEGER, + result TEXT, + hero TEXT, + hero_level INTEGER, + difficulty TEXT, + duration INTEGER, + kills INTEGER DEFAULT 0, + deaths INTEGER DEFAULT 0, + score INTEGER DEFAULT 0, + outgoing_damage REAL DEFAULT 0, + incoming_damage REAL DEFAULT 0, + items TEXT, + modifiers TEXT, + aghanim_scepter INTEGER DEFAULT 0, + aghanim_shard INTEGER DEFAULT 0, + gold_earned INTEGER DEFAULT 0, + session_id TEXT, + created_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS battle_passes ( + steam_id TEXT PRIMARY KEY, + level INTEGER DEFAULT 0, + experience INTEGER DEFAULT 0, + has_premium INTEGER DEFAULT 0, + claimed_rewards TEXT DEFAULT '[]', + claimed_premium_rewards TEXT DEFAULT '[]', + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS battle_pass_quests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + steam_id TEXT NOT NULL, + quest_id TEXT NOT NULL, + type TEXT NOT NULL, + name TEXT, + description TEXT, + progress INTEGER DEFAULT 0, + target INTEGER DEFAULT 1, + completed INTEGER DEFAULT 0, + claimed INTEGER DEFAULT 0, + reward_exp INTEGER DEFAULT 0, + reward_free_currency INTEGER DEFAULT 0, + quality TEXT, + npc TEXT, + target_item TEXT, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS purchases ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + steam_id TEXT NOT NULL, + item_id TEXT NOT NULL, + item_category TEXT, + card_id INTEGER, + price_free INTEGER DEFAULT 0, + price_donate INTEGER DEFAULT 0, + price_dust INTEGER DEFAULT 0, + created_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS active_effects ( + steam_id TEXT PRIMARY KEY, + effects TEXT DEFAULT '{}', + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS promo_codes ( + code TEXT PRIMARY KEY, + free_currency INTEGER DEFAULT 0, + donate_currency INTEGER DEFAULT 0, + dust_currency INTEGER DEFAULT 0, + max_uses INTEGER DEFAULT 1, + current_uses INTEGER DEFAULT 0, + expires_at TEXT + ); + + CREATE TABLE IF NOT EXISTS promo_redemptions ( + steam_id TEXT, + code TEXT, + redeemed_at TEXT DEFAULT (datetime('now')), + PRIMARY KEY (steam_id, code) + ); + + CREATE TABLE IF NOT EXISTS card_levels ( + steam_id TEXT PRIMARY KEY, + card_levels TEXT DEFAULT '{}', + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS decks ( + steam_id TEXT, + deck_index INTEGER, + name TEXT DEFAULT 'My Deck', + cards TEXT DEFAULT '[]', + updated_at TEXT DEFAULT (datetime('now')), + PRIMARY KEY (steam_id, deck_index) + ); + + CREATE TABLE IF NOT EXISTS equipment ( + steam_id TEXT PRIMARY KEY, + equipment TEXT DEFAULT '{}', + updated_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS arsenal_loadouts ( + steam_id TEXT, + hero_name TEXT, + loadout TEXT DEFAULT '{}', + updated_at TEXT DEFAULT (datetime('now')), + PRIMARY KEY (steam_id, hero_name) + ); + + CREATE TABLE IF NOT EXISTS arsenal_inventory ( + steam_id TEXT, + instance_id TEXT, + item_name TEXT, + quality TEXT, + upgrade_level INTEGER DEFAULT 0, + serial INTEGER, + global_serial INTEGER, + owner_name TEXT, + pinned INTEGER DEFAULT 0, + favorite INTEGER DEFAULT 0, + stats TEXT DEFAULT '[]', + PRIMARY KEY (steam_id, instance_id) + ); + + CREATE TABLE IF NOT EXISTS arsenal_market_listings ( + listing_id TEXT PRIMARY KEY, + steam_id TEXT NOT NULL, + instance_id TEXT, + item_name TEXT, + quality TEXT, + upgrade_level INTEGER DEFAULT 0, + serial INTEGER, + global_serial INTEGER, + price_free INTEGER DEFAULT 0, + status TEXT DEFAULT 'active', + created_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS arsenal_market_sales ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + listing_id TEXT, + seller_steam_id TEXT, + buyer_steam_id TEXT, + item_name TEXT, + price_free INTEGER, + created_at TEXT DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS death_sentence_contracts ( + steam_id TEXT PRIMARY KEY, + contracts TEXT DEFAULT '{}', + updated_at TEXT DEFAULT (datetime('now')) + ); + `); +} diff --git a/backend/src/lib/seed.ts b/backend/src/lib/seed.ts new file mode 100644 index 0000000..83aea4f --- /dev/null +++ b/backend/src/lib/seed.ts @@ -0,0 +1,28 @@ +import { getDb } from './db'; + +export function seedDatabase() { + const db = getDb(); + const count = db.prepare('SELECT COUNT(*) as c FROM promo_codes').get() as { c: number }; + if (count.c > 0) return; + + const insert = db.prepare(` + INSERT INTO promo_codes (code, free_currency, donate_currency, dust_currency, max_uses, expires_at) + VALUES (?, ?, ?, ?, ?, ?) + `); + + const codes = [ + ['WELCOME100', 100, 0, 0, 100, null], + ['ZOMBIE500', 500, 50, 0, 50, null], + ['DONATE100', 0, 100, 0, 20, null], + ['DUST250', 0, 0, 250, 30, null], + ]; + + const tx = db.transaction(() => { + for (const c of codes) { + insert.run(...c); + } + }); + tx(); + + console.log('Database seeded with promo codes'); +}