From 39f8220eb7111ff140c9e6570d8f16055da405e6 Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Sat, 11 Apr 2026 00:20:15 -0400 Subject: [PATCH] feat: rewrite seed-dev-data for MariaDB with test users and campaign members Co-Authored-By: Claude Sonnet 4.6 --- server/src/seed-dev-data.ts | 353 ++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 194 deletions(-) diff --git a/server/src/seed-dev-data.ts b/server/src/seed-dev-data.ts index e5b2208..d9f80fa 100644 --- a/server/src/seed-dev-data.ts +++ b/server/src/seed-dev-data.ts @@ -1,210 +1,175 @@ +import bcrypt from "bcrypt"; +import type { ResultSetHeader, RowDataPacket } from "mysql2"; import db from "./db.js"; +import { SEED_ITEMS } from "./seed-items.js"; +import { SEED_TALENTS } from "./seed-talents.js"; -export function seedDevData() { - const hasCampaigns = ( - db.prepare("SELECT COUNT(*) as c FROM campaigns").get() as { c: number } - ).c; - if (hasCampaigns > 0) return; +export async function seedDevData(): Promise { + const [userRows] = await db.execute( + "SELECT COUNT(*) as c FROM users" + ); + if ((userRows[0] as { c: number }).c > 0) return; + + const passwordHash = await bcrypt.hash("password", 12); + + // Create DM user + const [dmResult] = await db.execute( + "INSERT INTO users (email, username, password_hash) VALUES (?, ?, ?)", + ["dm@darkwatch.test", "DungeonMaster", passwordHash] + ); + const dmId = dmResult.insertId; + + // Create Player user + const [playerResult] = await db.execute( + "INSERT INTO users (email, username, password_hash) VALUES (?, ?, ?)", + ["player@darkwatch.test", "Adventurer", passwordHash] + ); + const playerId = playerResult.insertId; // Create campaign - db.prepare( - "INSERT INTO campaigns (name) VALUES ('Tomb of the Serpent King')", - ).run(); + const [campaignResult] = await db.execute( + "INSERT INTO campaigns (name) VALUES (?)", + ["Tomb of the Serpent King"] + ); + const campaignId = campaignResult.insertId; - const insertChar = db.prepare(` - INSERT INTO characters (campaign_id, name, class, ancestry, level, xp, hp_current, hp_max, ac, alignment, title, background, deity, languages, gp, sp, cp, color) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `); - - const insertStat = db.prepare( - "INSERT INTO character_stats (character_id, stat_name, value) VALUES (?, ?, ?)", + // Add DM as dm, player as player + await db.execute( + "INSERT INTO campaign_members (campaign_id, user_id, role) VALUES (?, ?, 'dm')", + [campaignId, dmId] + ); + await db.execute( + "INSERT INTO campaign_members (campaign_id, user_id, role) VALUES (?, ?, 'player')", + [campaignId, playerId] ); - const insertGear = db.prepare( - "INSERT INTO character_gear (character_id, name, type, slot_count, properties, effects, game_item_id) VALUES (?, ?, ?, ?, ?, ?, ?)", + // Create invite token for testing + await db.execute( + "INSERT INTO campaign_invites (campaign_id, token, created_by) VALUES (?, ?, ?)", + [campaignId, "dev-invite-token-abc123", dmId] ); - const insertTalent = db.prepare( - "INSERT INTO character_talents (character_id, name, description, effect, game_talent_id) VALUES (?, ?, ?, ?, ?)", - ); - - // --- Limpie --- - const limpie = insertChar.run( - 1, - "Limpie", - "Thief", - "Goblin", - 1, - 6, - 1, - 1, - 10, - "Lawful", - "Footpad", - "Chirurgeo", - "Madeera the Covenant", - "Common, Goblin", - 8, - 0, - 0, - "hsl(45, 60%, 65%)", - ); - const limpieId = limpie.lastInsertRowid; - - // Limpie stats - const limpieStats: [string, number][] = [ - ["STR", 11], - ["DEX", 11], - ["CON", 8], - ["INT", 10], - ["WIS", 14], - ["CHA", 10], - ]; - for (const [name, value] of limpieStats) { - insertStat.run(limpieId, name, value); + // Seed game_items from seed-items.ts + for (const item of SEED_ITEMS) { + await db.execute( + "INSERT INTO game_items (name, type, slot_count, effects, properties) VALUES (?, ?, ?, ?, ?)", + [ + item.name, + item.type, + item.slot_count, + JSON.stringify(item.effects), + JSON.stringify(item.properties), + ] + ); } - // Limpie gear - insertGear.run( - limpieId, - "Shortsword", - "weapon", - 1, - "{}", - '{"damage":"1d6","melee":true,"stat":"STR"}', - null, - ); - insertGear.run( - limpieId, - "Shortbow", - "weapon", - 1, - "{}", - '{"damage":"1d4","ranged":true,"stat":"DEX","two_handed":true}', - null, - ); - insertGear.run( - limpieId, - "Leather Armor", - "armor", - 1, - "{}", - '{"ac_base":11,"ac_dex":true}', - null, - ); - insertGear.run(limpieId, "Shield", "armor", 1, "{}", '{"ac_bonus":2}', null); - insertGear.run(limpieId, "Arrows (20)", "gear", 1, "{}", "{}", null); - insertGear.run(limpieId, "Torch", "gear", 1, "{}", "{}", null); - insertGear.run(limpieId, "Rations", "gear", 1, "{}", "{}", null); - insertGear.run(limpieId, "Rope (60ft)", "gear", 1, "{}", "{}", null); - insertGear.run(limpieId, "Thieves' Tools", "gear", 1, "{}", "{}", null); - - // Limpie talents - insertTalent.run( - limpieId, - "Backstab", - "Extra 1 + half level (round down) weapon dice of damage with surprise attacks", - '{"damage_bonus_surprise":true}', - null, - ); - insertTalent.run( - limpieId, - "Thievery", - "Trained in climbing, sneaking, hiding, disguise, finding & disabling traps, delicate tasks", - "{}", - null, - ); - insertTalent.run( - limpieId, - "Keen Senses", - "Can't be surprised", - '{"immune_surprise":true}', - null, - ); - - // --- Brynn --- - const brynn = insertChar.run( - 1, - "Brynn", - "Fighter", - "Human", - 2, - 15, - 8, - 10, - 10, - "Neutral", - "the Bold", - "Soldier", - "Gede", - "Common", - 25, - 5, - 10, - "hsl(200, 60%, 65%)", - ); - const brynnId = brynn.lastInsertRowid; - - // Brynn stats - const brynnStats: [string, number][] = [ - ["STR", 16], - ["DEX", 12], - ["CON", 14], - ["INT", 8], - ["WIS", 10], - ["CHA", 11], - ]; - for (const [name, value] of brynnStats) { - insertStat.run(brynnId, name, value); + // Seed game_talents from seed-talents.ts + for (const talent of SEED_TALENTS) { + await db.execute( + "INSERT INTO game_talents (name, source, description, effect) VALUES (?, ?, ?, ?)", + [ + talent.name, + talent.source, + talent.description, + JSON.stringify(talent.effect), + ] + ); } - // Brynn gear - insertGear.run( - brynnId, - "Longsword", - "weapon", - 1, - "{}", - '{"damage":"1d8","melee":true,"stat":"STR"}', - null, - ); - insertGear.run( - brynnId, - "Chainmail", - "armor", - 1, - "{}", - '{"ac_base":13,"ac_dex":true}', - null, - ); - insertGear.run(brynnId, "Shield", "armor", 1, "{}", '{"ac_bonus":2}', null); - insertGear.run( - brynnId, - "Javelin", - "weapon", - 1, - "{}", - '{"damage":"1d4","melee":true,"stat":"STR","thrown":true,"range":"far"}', - null, - ); - insertGear.run(brynnId, "Torch", "gear", 1, "{}", "{}", null); - insertGear.run(brynnId, "Rations", "gear", 1, "{}", "{}", null); + async function createCharacter( + charCampaignId: number, + userId: number, + data: { + name: string; class: string; ancestry: string; level: number; xp: number; + hp_current: number; hp_max: number; ac: number; alignment: string; + title: string; background: string; deity: string; languages: string; + gp: number; sp: number; cp: number; color: string; + } + ): Promise { + const [r] = await db.execute( + `INSERT INTO characters + (campaign_id, user_id, name, class, ancestry, level, xp, hp_current, hp_max, + ac, alignment, title, background, deity, languages, gp, sp, cp, color) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [charCampaignId, userId, data.name, data.class, data.ancestry, data.level, data.xp, + data.hp_current, data.hp_max, data.ac, data.alignment, data.title, + data.background, data.deity, data.languages, data.gp, data.sp, data.cp, data.color] + ); + return r.insertId; + } - // Brynn talents - insertTalent.run( - brynnId, - "Weapon Mastery", - "+1 to attack and damage with longswords", - '{"attack_bonus":1,"damage_bonus":1}', - null, - ); - insertTalent.run( - brynnId, - "Grit", - "+2 HP and +1 HP each level", - '{"hp_bonus":2,"hp_per_level":1}', - null, - ); + async function addStats(charId: number, stats: [string, number][]) { + for (const [name, value] of stats) { + await db.execute( + "INSERT INTO character_stats (character_id, stat_name, value) VALUES (?, ?, ?)", + [charId, name, value] + ); + } + } - console.log("Dev data seeded: campaign + 2 characters (Limpie & Brynn)"); + async function addGear(charId: number, items: { name: string; type: string; slot_count: number; properties: string; effects: string }[]) { + for (const item of items) { + await db.execute( + "INSERT INTO character_gear (character_id, name, type, slot_count, properties, effects) VALUES (?, ?, ?, ?, ?, ?)", + [charId, item.name, item.type, item.slot_count, item.properties, item.effects] + ); + } + } + + async function addTalents(charId: number, talents: { name: string; description: string; effect: string }[]) { + for (const t of talents) { + await db.execute( + "INSERT INTO character_talents (character_id, name, description, effect) VALUES (?, ?, ?, ?)", + [charId, t.name, t.description, t.effect] + ); + } + } + + // --- Limpie (player's character 1) --- + const limpieId = await createCharacter(campaignId, playerId, { + name: "Limpie", class: "Thief", ancestry: "Goblin", level: 1, xp: 6, + hp_current: 1, hp_max: 1, ac: 10, alignment: "Lawful", title: "Footpad", + background: "Chirurgeo", deity: "Madeera the Covenant", + languages: "Common, Goblin", gp: 8, sp: 0, cp: 0, color: "hsl(45, 60%, 65%)", + }); + await addStats(limpieId, [["STR",11],["DEX",11],["CON",8],["INT",10],["WIS",14],["CHA",10]]); + await addGear(limpieId, [ + { name: "Shortsword", type: "weapon", slot_count: 1, properties: "{}", effects: '{"damage":"1d6","melee":true,"stat":"STR"}' }, + { name: "Shortbow", type: "weapon", slot_count: 1, properties: "{}", effects: '{"damage":"1d4","ranged":true,"stat":"DEX","two_handed":true}' }, + { name: "Leather Armor", type: "armor", slot_count: 1, properties: "{}", effects: '{"ac_base":11,"ac_dex":true}' }, + { name: "Shield", type: "armor", slot_count: 1, properties: "{}", effects: '{"ac_bonus":2}' }, + { name: "Arrows (20)", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + { name: "Torch", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + { name: "Rations", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + { name: "Rope (60ft)", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + { name: "Thieves' Tools", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + ]); + await addTalents(limpieId, [ + { name: "Backstab", description: "Extra 1 + half level (round down) weapon dice of damage with surprise attacks", effect: '{"damage_bonus_surprise":true}' }, + { name: "Thievery", description: "Trained in climbing, sneaking, hiding, disguise, finding & disabling traps, delicate tasks", effect: "{}" }, + { name: "Keen Senses", description: "Can't be surprised", effect: '{"immune_surprise":true}' }, + ]); + + // --- Brynn (player's character 2) --- + const brynnId = await createCharacter(campaignId, playerId, { + name: "Brynn", class: "Fighter", ancestry: "Human", level: 2, xp: 15, + hp_current: 8, hp_max: 10, ac: 10, alignment: "Neutral", title: "the Bold", + background: "Soldier", deity: "Gede", languages: "Common", + gp: 25, sp: 5, cp: 10, color: "hsl(200, 60%, 65%)", + }); + await addStats(brynnId, [["STR",16],["DEX",12],["CON",14],["INT",8],["WIS",10],["CHA",11]]); + await addGear(brynnId, [ + { name: "Longsword", type: "weapon", slot_count: 1, properties: "{}", effects: '{"damage":"1d8","melee":true,"stat":"STR"}' }, + { name: "Chainmail", type: "armor", slot_count: 1, properties: "{}", effects: '{"ac_base":13,"ac_dex":true}' }, + { name: "Shield", type: "armor", slot_count: 1, properties: "{}", effects: '{"ac_bonus":2}' }, + { name: "Javelin", type: "weapon", slot_count: 1, properties: "{}", effects: '{"damage":"1d4","melee":true,"stat":"STR","thrown":true,"range":"far"}' }, + { name: "Torch", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + { name: "Rations", type: "gear", slot_count: 1, properties: "{}", effects: "{}" }, + ]); + await addTalents(brynnId, [ + { name: "Weapon Mastery", description: "+1 to attack and damage with longswords", effect: '{"attack_bonus":1,"damage_bonus":1}' }, + { name: "Grit", description: "+2 HP and +1 HP each level", effect: '{"hp_bonus":2,"hp_per_level":1}' }, + ]); + + console.log("Dev data seeded: 2 users, 1 campaign, 2 characters (Limpie & Brynn)"); }