step 9.2: done

This commit is contained in:
bepis
2026-10-21 10:01:35 +11:00
parent aeea0b9aaf
commit d8a1e88150
4 changed files with 160 additions and 13 deletions
+13 -9
View File
@@ -4,8 +4,8 @@
> If this file disagrees with the code, trust the code and fix this file.
## 1. Where we are
- **Last completed step:** 9.1 (Security checklist walk — fixed innerHTML + added rate limiter) — TICKED in STATUS.md
- **Next step:** Phase 9 — 9.2 (Add `npm run seed:dev` creating a demo account and hero)
- **Last completed step:** 9.2 (seed-dev script — creates demo account + hero) — TICKED in STATUS.md
- **Next step:** Phase 9 — 9.3 (Global error pages / friendly UI error toasts. Loading states.)
- **Updated:** 2026-10-21
- **Was the last session cut off mid-task?** No
@@ -15,6 +15,7 @@
| `npm install` | succeeds |
| `npm run migrate` | applies 001–006 / "nothing to do" |
| `npm test` | **172 passing, 0 failing** (85 unit + 87 integration) |
| `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear |
## 3. What exists now (files that matter)
- `public/profile.html` — Public hero profile page (reads ?name=, displays stats/equipment).
@@ -23,6 +24,7 @@
- `public/js/game.js` — hunt UI + inventory UI + expedition UI.
- `public/shop.html` — Dedicated shop page.
- `public/login.html`, `public/register.html`, `public/create-hero.html` — Auth & hero creation.
- `scripts/seed-dev.js` — Creates demo account + hero + starter gear (idempotent, skips in production).
- `src/services/heroProfileService.js` — `getHeroProfile(heroName)`.
- `src/routes/heroProfile.js` — `GET /api/heroes/:name` (public).
- `src/services/leaderboardService.js` — `getLeaderboard(type, page, limit)`.
@@ -53,7 +55,7 @@
- Equipment bonuses not yet passed into combat (Issue #5 in STATUS.md).
- Starter gear (GAME_DESIGN.md §Items) documented but not auto-created on character creation.
- `GET /api/zones` documented in API.md but not implemented (Issue #2).
- Step 9.2 (`npm run seed:dev`) not yet implemented.
- Step 9.3 (error pages/toasts/loading states) not yet implemented.
## 5. Decisions & deviations from the docs
- D-004..D-007 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock).
@@ -84,10 +86,12 @@
- Hero profile endpoint is PUBLIC — no `requireAuth` middleware.
- Profile page is public: no auth.js guard needed; uses ?name= query param for lookup.
- Rate limiters in tests: set `NODE_ENV=test` to get 1000 req/window limit; otherwise tests fail with 429.
- `seed-dev.js` must check `NODE_ENV=production` before any `require()` that loads config (config throws on placeholder SECRET).
## 7. Next-step briefing (9.2)
- **Goal:** Add `npm run seed:dev` script that creates a demo account and hero on an empty DB.
- **Docs:** None specific — follow existing patterns (migrate.js, authService, characterService).
- Should: run migrations, then insert a test user + character, optionally give starter gear.
- Should be safe to re-run (check if demo user exists first).
- Should NOT be used in production (skip if NODE_ENV=production or check for a flag).
## 7. Next-step briefing (9.3)
- **Goal:** Add global error pages / friendly UI error toasts. Loading states.
- **Docs:** UI_GUIDE, SECURITY (for error handling), TESTING.
- Should: add a generic 404/500 error page template in `public/`.
- Should: add a toast notification system (JS + CSS) for user-facing errors.
- Should: add loading spinners/states for async operations (hunt, buy, etc.).
- Should NOT introduce new dependencies — use vanilla HTML/CSS/JS.
+5 -3
View File
@@ -8,9 +8,9 @@
## Current Focus
- **Phase:** 9
- **Step:** 9.2
- **Step:** 9.3
- **Blocked?** No
- **Last verified working command:** `npm test` — 172/172 passing (85 unit + 87 integration), 2026-10-21 step 9.1
- **Last verified working command:** `npm test` — 172/172 passing (85 unit + 87 integration), 2026-10-21 step 9.2
## Project Health
| Item | State |
@@ -107,7 +107,8 @@
- [x] **9.1** Walk the SECURITY.md checklist; fix gaps.
*Acceptance:* all SECURITY.md checklist items verified or fixed.
*Fixed:* removed `innerHTML` with dynamic data in `game.js` (renderActiveExpedition) and `profile.js` (renderProfile stats) — converted to createElement/textContent. Added missing general `/api` rate limiter (120/min/IP per SECURITY.md spec).
- [ ] **9.2** Add `npm run seed:dev` creating a demo account and hero.
- [x] **9.2** Add `npm run seed:dev` creating a demo account and hero.
*Acceptance:* `npm run seed:dev` creates migrations + demo user + hero + starter gear; safe to re-run; skipped in production.
- [ ] **9.3** Global error pages / friendly UI error toasts. Loading states.
- [ ] **9.4** Mobile-responsive pass (see UI_GUIDE).
- [ ] **9.5** Final full run of "Definition of playable" from NOTES.md §7. Record results below.
@@ -190,3 +191,4 @@ _Format:_ `YYYY-MM-DD | step | what changed | result`
- 2026-10-20 | 2.4 | Created `public/create-hero.html`, `public/game.html`, `public/js/create-hero.js`, `public/js/game.js` | app loads
- 2026-10-21 | 8.2 | Created `src/services/heroProfileService.js` (getHeroProfile), `src/routes/heroProfile.js` (GET /api/heroes/:name, public), integrated into app.js; 5 integration tests covering public lookup, case-insensitivity, equipped items, 404, no-auth access | 167/167 tests pass
- 2026-10-21 | 9.1 | Walked SECURITY.md checklist: fixed `innerHTML` with dynamic data in `game.js` (renderActiveExpedition) and `profile.js` (renderProfile stats) — converted to createElement/textContent; added missing general `/api` rate limiter (120/min/IP per SECURITY.md spec); verified all 9 checklist items pass | 172/172 tests pass
- 2026-10-21 | 9.2 | Created `scripts/seed-dev.js`: runs migrations, creates demo user "demo"/"demo123", hero "Aldric" (warrior), grants starter gear (rusty sword, leather jerkin, 2 healing potions); idempotent; skips in production | 172/172 tests pass
+2 -1
View File
@@ -12,7 +12,8 @@
"dev": "node --watch src/server.js",
"test": "NODE_ENV=test node --test --test-concurrency=1",
"migrate": "node scripts/migrate.js",
"seed": "node scripts/seed.js"
"seed": "node scripts/seed.js",
"seed:dev": "node scripts/seed-dev.js"
},
"keywords": [],
"type": "commonjs",
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env node
// seed-dev.js — Create a demo account + hero for local testing.
// Safe to re-run (skips if demo user already exists).
// Skipped entirely when NODE_ENV=production.
// NOTE: NODE_ENV check must happen before any require() that loads config,
// because config throws on placeholder SESSION_SECRET in production.
if (process.env.NODE_ENV === 'production') {
console.log('seed-dev: NODE_ENV=production — skipping.');
process.exit(0);
}
const bcrypt = require('bcryptjs');
const { runMigrations } = require('./migrate');
const { getDb } = require('../src/db');
const time = require('../src/utils/time');
const DEMO_USERNAME = 'demo';
const DEMO_PASSWORD = 'demo123';
const DEMO_HERO_NAME = 'Aldric';
const DEMO_CLASS = 'warrior';
// Starter gear: a basic weapon + armor + potion for the demo hero.
const STARTER_ITEMS = [
{ slug: 'rusty-sword', qty: 1 }, // weapon, common, min_level 1
{ slug: 'leather-jerkin', qty: 1 }, // armor, common, min_level 1
{ slug: 'minor-healing-potion', qty: 2 }, // consumable, stackable
];
function getOrCreateDemoUser(db) {
const stmt = db.prepare(
'SELECT id, username FROM users WHERE username = ? COLLATE NOCASE'
);
const existing = stmt.get(DEMO_USERNAME);
if (existing) {
console.log(` Demo user "${DEMO_USERNAME}" already exists (id=${existing.id}).`);
return existing;
}
const passwordHash = bcrypt.hashSync(DEMO_PASSWORD, 12);
const result = db.prepare(
'INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, ?)'
).run(DEMO_USERNAME, passwordHash, time.now());
console.log(` Created demo user "${DEMO_USERNAME}" (id=${result.lastInsertRowid}).`);
return { id: result.lastInsertRowid, username: DEMO_USERNAME };
}
function getOrCreateDemoHero(db, userId) {
const heroCheck = db.prepare(
'SELECT id FROM characters WHERE user_id = ?'
).get(userId);
if (heroCheck) {
console.log(` Demo hero already exists for user ${userId}.`);
return null;
}
const heroNameCheck = db.prepare(
'SELECT id FROM characters WHERE name = ? COLLATE NOCASE'
).get(DEMO_HERO_NAME);
if (heroNameCheck) {
console.log(` Hero name "${DEMO_HERO_NAME}" already taken.`);
return null;
}
// Get base stats for the class
const { getBaseStats } = require('../src/game/classes');
const { calcDerived } = require('../src/game/stats');
const baseStats = getBaseStats(DEMO_CLASS);
const hpCurrent = calcDerived(baseStats, 1, DEMO_CLASS).maxHp;
const { ENERGY_MAX } = require('../src/game/stats');
const now = time.now();
const result = db.prepare(
`INSERT INTO characters
(user_id, name, class, level, xp, gold, str, agi, int, vit,
unspent_points, hp_current, energy, energy_updated_at, kills, deaths, created_at)
VALUES (?, ?, ?, 1, 0, 100, ?, ?, ?, ?, 0, ?, ?, ?, 0, 0, ?)`
).run(
userId, DEMO_HERO_NAME,
DEMO_CLASS,
baseStats.str, baseStats.agi, baseStats.int, baseStats.vit,
hpCurrent, ENERGY_MAX, now, now
);
console.log(` Created demo hero "${DEMO_HERO_NAME}" (id=${result.lastInsertRowid}, class=${DEMO_CLASS}).`);
return { id: result.lastInsertRowid };
}
function giveStarterGear(db, characterId) {
const itemIds = [];
for (const { slug, qty } of STARTER_ITEMS) {
const item = db.prepare('SELECT id FROM items WHERE slug = ?').get(slug);
if (!item) {
console.warn(` WARNING: item "${slug}" not found in database.`);
continue;
}
itemIds.push({ itemId: item.id, qty });
}
const insertInv = db.prepare(
'INSERT INTO inventory (character_id, item_id, quantity, acquired_at) VALUES (?, ?, ?, ?)'
);
for (const { itemId, qty } of itemIds) {
insertInv.run(characterId, itemId, qty, time.now());
}
console.log(` Granted ${itemIds.length} starter item(s) to hero ${characterId}.`);
}
function seedDev() {
console.log('seed-dev: running migrations...');
runMigrations();
const db = getDb();
console.log('seed-dev: seeding demo account...');
const user = getOrCreateDemoUser(db);
const hero = getOrCreateDemoHero(db, user.id);
if (hero) {
giveStarterGear(db, hero.id);
}
console.log('');
console.log('═══════════════════════════════════════');
console.log(' Demo account ready!');
console.log(` Username: ${DEMO_USERNAME}`);
console.log(` Password: ${DEMO_PASSWORD}`);
console.log('═══════════════════════════════════════');
console.log('');
}
// Run if called directly
if (require.main === module) {
seedDev();
process.exit(0);
}
module.exports = { seedDev };