diff --git a/server/src/db.js b/server/src/db.js new file mode 100644 index 0000000..a6be4d8 --- /dev/null +++ b/server/src/db.js @@ -0,0 +1,101 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import pg from 'pg'; +import bcrypt from 'bcryptjs'; + +const { Pool } = pg; + +const pool = new Pool({ + connectionString: process.env.DATABASE_URL +}); + +export async function query(q, params) { + const client = await pool.connect(); + try { + return await client.query(q, params); + } finally { + client.release(); + } +} + +// init DB from schema + seed +export async function init() { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8'); + await query(schema); + const seed = fs.readFileSync(path.join(__dirname, 'seed.sql'), 'utf8'); + await query(seed); + console.log('DB initialized.'); +} + +// Models (simple) + +export const Users = { + async create(username, password) { + const passhash = await bcrypt.hash(password, 10); + const { rows } = await query( + 'INSERT INTO users (username, passhash) VALUES ($1,$2) RETURNING id, username', + [username, passhash] + ); + // create character with same name + await query( + 'INSERT INTO characters (user_id, name) VALUES ($1,$2)', + [rows[0].id, username] + ); + return rows[0]; + }, + async verify(username, password) { + const { rows } = await query( + 'SELECT * FROM users WHERE username=$1', + [username] + ); + if (!rows[0]) return null; + const ok = await bcrypt.compare(password, rows[0].passhash); + return ok ? rows[0] : null; + } +}; + +export const Characters = { + async getByUserId(userId) { + const { rows } = await query( + 'SELECT * FROM characters WHERE user_id=$1 LIMIT 1', + [userId] + ); + return rows[0] || null; + }, + async addXP(id, amount) { + await query('UPDATE characters SET xp = xp + $1 WHERE id=$2', [amount, id]); + } +}; + +export const Inventory = { + async all(characterId) { + const { rows } = await query( + 'SELECT item_key, qty FROM inventory WHERE character_id=$1', + [characterId] + ); + return rows; + }, + async add(characterId, itemKey, qty) { + // upsert + const text = ` + INSERT INTO inventory (character_id, item_key, qty) + VALUES ($1,$2,$3) + ON CONFLICT (character_id, item_key) + DO UPDATE SET qty = inventory.qty + EXCLUDED.qty + `; + await query(text, [characterId, itemKey, qty]); + } +}; + +// allow manual init: node src/db.js init +if (process.argv[2] === 'init') { + init() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); +}