step 4.5: done
This commit is contained in:
@@ -4,6 +4,8 @@ const { createSessionMiddleware, requireAuth } = require('./middleware/session')
|
||||
const { registerLimiter, loginLimiter, routes: authRoutes } = require('./routes/auth');
|
||||
const { routes: characterRoutes } = require('./routes/character');
|
||||
const { routes: huntRoutes } = require('./routes/hunt');
|
||||
const { routes: zonesRoutes } = require('./routes/zones');
|
||||
const { routes: combatLogRoutes } = require('./routes/combatLog');
|
||||
const { AppError } = require('./utils/errors');
|
||||
|
||||
function createApp() {
|
||||
@@ -44,6 +46,12 @@ function createApp() {
|
||||
// Hunt routes (require session)
|
||||
app.post('/api/hunt', requireAuth, huntRoutes.postHunt);
|
||||
|
||||
// Zone routes (require session)
|
||||
app.get('/api/zones', requireAuth, zonesRoutes.getZones);
|
||||
|
||||
// Combat log routes (require session)
|
||||
app.get('/api/combat-log', requireAuth, combatLogRoutes.getCombatLog);
|
||||
|
||||
// Central error handler
|
||||
app.use((err, _req, res, _next) => {
|
||||
// Log stack traces server-side only
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Combat log routes: GET /api/combat-log (recent fights for the logged-in user's character).
|
||||
const zod = require('zod');
|
||||
const { getCombatLog } = require('../services/combatLogService');
|
||||
const { requireAuth } = require('../middleware/session');
|
||||
const { getCharacterIdForUser } = require('../services/characterService');
|
||||
|
||||
function wrap(fn) {
|
||||
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
||||
}
|
||||
|
||||
const logSchema = zod.object({
|
||||
limit: zod.coerce.number().int().min(1).max(100).optional(),
|
||||
});
|
||||
|
||||
async function handleGetCombatLog(req, res) {
|
||||
const parsed = logSchema.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
const { AppError } = require('../utils/errors');
|
||||
throw new AppError('VALIDATION_ERROR', 'Request validation failed.', 400);
|
||||
}
|
||||
|
||||
const characterId = getCharacterIdForUser(req.session.userId);
|
||||
const limit = parsed.data.limit ?? 20;
|
||||
const logs = getCombatLog(characterId, limit);
|
||||
res.json({ ok: true, data: logs });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routes: {
|
||||
getCombatLog: wrap(handleGetCombatLog),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// Zone routes: GET /api/zones (list zones with unlocked flag).
|
||||
const zod = require('zod');
|
||||
const { listZones } = require('../services/zoneService');
|
||||
const { requireAuth } = require('../middleware/session');
|
||||
const { getCharacterProfile } = require('../services/characterService');
|
||||
|
||||
function wrap(fn) {
|
||||
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
||||
}
|
||||
|
||||
async function handleGetZones(req, res) {
|
||||
const profile = getCharacterProfile(req.session.userId);
|
||||
const zones = listZones(profile.character.level);
|
||||
res.json({ ok: true, data: zones });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routes: {
|
||||
getZones: wrap(handleGetZones),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// Combat log service: retrieve recent combat log entries for a character.
|
||||
const { getDb } = require('../db');
|
||||
|
||||
const stmtCache = new WeakMap();
|
||||
|
||||
function getStmts(db) {
|
||||
let s = stmtCache.get(db);
|
||||
if (!s) {
|
||||
s = {
|
||||
logs: db.prepare(
|
||||
`SELECT cl.id, cl.monster_id, cl.won, cl.rounds, cl.xp_gained, cl.gold_gained, cl.created_at,
|
||||
m.name AS monster_name, m.level AS monster_level
|
||||
FROM combat_log cl
|
||||
JOIN monsters m ON m.id = cl.monster_id
|
||||
WHERE cl.character_id = ?
|
||||
ORDER BY cl.id DESC
|
||||
LIMIT ?`
|
||||
),
|
||||
};
|
||||
stmtCache.set(db, s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} characterId
|
||||
* @param {number} [limit] - max entries, default 20
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getCombatLog(characterId, limit = 20) {
|
||||
const db = getDb();
|
||||
const s = getStmts(db);
|
||||
return s.logs.all(characterId, limit).map((row) => ({
|
||||
id: row.id,
|
||||
monsterName: row.monster_name,
|
||||
monsterLevel: row.monster_level,
|
||||
won: Boolean(row.won),
|
||||
rounds: row.rounds,
|
||||
xpGained: row.xp_gained,
|
||||
goldGained: row.gold_gained,
|
||||
createdAt: row.created_at,
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = { getCombatLog };
|
||||
@@ -0,0 +1,31 @@
|
||||
// Zone service: list zones with unlocked flag for a given character level.
|
||||
const { getDb } = require('../db');
|
||||
|
||||
// Prepared statements are cached PER database handle.
|
||||
const stmtCache = new WeakMap();
|
||||
|
||||
function getStmts(db) {
|
||||
let s = stmtCache.get(db);
|
||||
if (!s) {
|
||||
s = {
|
||||
zones: db.prepare('SELECT id, slug, name, description, min_level, energy_cost FROM zones ORDER BY min_level, id'),
|
||||
};
|
||||
stmtCache.set(db, s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} characterLevel - hero's current level
|
||||
* @returns {Array<{id, slug, name, description, min_level, energy_cost, unlocked}>}
|
||||
*/
|
||||
function listZones(characterLevel) {
|
||||
const db = getDb();
|
||||
const s = getStmts(db);
|
||||
return s.zones.all().map((zone) => ({
|
||||
...zone,
|
||||
unlocked: characterLevel >= zone.min_level,
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = { listZones };
|
||||
Reference in New Issue
Block a user