step 9.2: done
This commit is contained in:
+13
-9
@@ -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.
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user