diff --git a/public/js/game.js b/public/js/game.js index 9152fb1..f4f159c 100644 --- a/public/js/game.js +++ b/public/js/game.js @@ -19,6 +19,10 @@ function switchTab(tab) { document.getElementById('hunt-tab').style.display = tab === 'hunt' ? '' : 'none'; document.getElementById('expedition-tab').style.display = tab === 'expedition' ? '' : 'none'; document.getElementById('inventory-tab').style.display = tab === 'inventory' ? '' : 'none'; + if (tab === 'expedition') { + loadExpeditions(); + startExpeditionSync(); + } } /** @@ -136,6 +140,7 @@ async function loadCharacter() { } const { character, derived, energy } = data; + window.heroLevel = character.level; // Show stats, hide loading document.getElementById('hero-loading').style.display = 'none'; @@ -177,7 +182,7 @@ async function loadZones() { const zoneList = document.getElementById('zone-list'); try { const data = await api.get('/zones'); - if (!data || !data.ok) { + if (!Array.isArray(data)) { zoneList.innerHTML = ''; const p = document.createElement('p'); p.className = 'placeholder'; @@ -186,7 +191,7 @@ async function loadZones() { return; } - const zones = data.data; + const zones = data; zoneList.innerHTML = ''; for (const zone of zones) { @@ -256,18 +261,18 @@ async function handleHunt(e) { try { const data = await api.post('/hunt', { zoneId }); - if (!data || !data.ok) { + if (!data) { showToast(data?.error?.message || 'Hunt failed.', 'error'); return; } - const result = data.data; + const result = data; displayHuntResult(result); loadCharacter(); loadCombatLog(); loadZones(); // refresh energy cost buttons } catch (err) { - showToast('Hunt failed.', 'error'); + showToast(err.message || 'Hunt failed.', 'error'); } finally { isHunting = false; // Re-enable all hunt buttons @@ -352,7 +357,7 @@ async function loadCombatLog() { const logList = document.getElementById('combat-log-list'); try { const data = await api.get('/combat-log?limit=10'); - if (!data || !data.ok) { + if (!Array.isArray(data)) { logList.innerHTML = ''; const p = document.createElement('p'); p.className = 'placeholder'; @@ -361,7 +366,7 @@ async function loadCombatLog() { return; } - const logs = data.data; + const logs = data; logList.innerHTML = ''; if (logs.length === 0) { @@ -595,8 +600,8 @@ async function handleItemClick(item) { } else { data = await api.post('/inventory/equip', { itemId: item.itemId, slot: item.type }); } - if (!data || !data.ok) { - showToast(data?.error?.message || 'Action failed.', 'error'); + if (!data) { + showToast('Action failed.', 'error'); return; } inventoryCache = data; @@ -611,8 +616,8 @@ async function handleItemClick(item) { async function handleUnequip(slot) { try { const data = await api.post('/inventory/unequip', { slot }); - if (!data || !data.ok) { - showToast(data?.error?.message || 'Unequip failed.', 'error'); + if (!data) { + showToast('Unequip failed.', 'error'); return; } inventoryCache = data; @@ -649,14 +654,13 @@ function formatDuration(sec) { async function loadExpeditions() { try { - const data = await api.get('/expeditions/active'); - if (!data || !data.ok) return; + const data = await api.get('/expeditions/active'); // null when no active expedition const statusEl = document.getElementById('expedition-status'); const defsEl = document.getElementById('expedition-defs'); - if (data.data) { - renderActiveExpedition(statusEl, defsEl, data.data); + if (data) { + renderActiveExpedition(statusEl, defsEl, data); } else { statusEl.innerHTML = ''; renderExpeditionDefs(defsEl); @@ -739,9 +743,9 @@ function renderExpeditionDefs(container) { // Fetch expedition definitions from the server api.get('/expeditions/defs').then(defsData => { - if (!defsData || !defsData.ok || !defsData.data) return; + if (!Array.isArray(defsData)) return; - const defs = defsData.data; + const defs = defsData; for (const def of defs) { const card = document.createElement('div'); card.className = 'expedition-def-card'; @@ -756,20 +760,20 @@ function renderExpeditionDefs(container) { const infoEl = document.createElement('div'); infoEl.className = 'expedition-info'; - infoEl.textContent = `${formatDuration(def.durationSec)} · ${def.energyCost}⚡ · Lv${def.min_level}+`; + infoEl.textContent = `${formatDuration(def.durationSec)} · ${def.energyCost}⚡ · Lv${def.minLevel}+`; const rewardsEl = document.createElement('div'); rewardsEl.className = 'expedition-rewards'; - rewardsEl.textContent = `+${def.xpReward} XP, ${def.goldMin}–${def.gold_max} Gold`; + rewardsEl.textContent = `+${def.xpReward} XP, ${def.goldMin}–${def.goldMax} Gold`; const btn = document.createElement('button'); btn.className = 'btn btn-expedition'; btn.textContent = `Start Expedition (${def.energyCost}⚡)`; - btn.disabled = window.heroLevel < def.min_level; + btn.disabled = window.heroLevel < def.minLevel; btn.dataset.defId = def.id; - if (window.heroLevel < def.min_level) { - btn.title = `Requires level ${def.min_level}`; + if (window.heroLevel < def.minLevel) { + btn.title = `Requires level ${def.minLevel}`; } card.appendChild(nameEl); @@ -796,15 +800,15 @@ async function handleStartExpedition(e) { try { const data = await api.post('/expeditions/start', { defId }); - if (!data || !data.ok) { - showToast(data?.error?.message || 'Failed to start expedition.', 'error'); + if (!data) { + showToast('Failed to start expedition.', 'error'); return; } showToast('Expedition started!', 'info'); loadExpeditions(); loadCharacter(); // refresh energy } catch (err) { - showToast('Failed to start expedition.', 'error'); + showToast(err.message || 'Failed to start expedition.', 'error'); } } @@ -818,17 +822,17 @@ async function handleClaimExpedition(e) { try { const data = await api.post('/expeditions/claim', { expeditionId }); - if (!data || !data.ok) { - showToast(data?.error?.message || 'Failed to claim expedition.', 'error'); + if (!data) { + showToast('Failed to claim expedition.', 'error'); return; } - const { xpGained, goldGained } = data.data; + const { xpGained, goldGained } = data; showToast(`Expedition complete! +${xpGained} XP, +${goldGained} Gold`, 'info'); loadExpeditions(); loadCharacter(); // refresh gold/XP } catch (err) { - showToast('Failed to claim expedition.', 'error'); + showToast(err.message || 'Failed to claim expedition.', 'error'); } } diff --git a/public/login.html b/public/login.html index bba8e25..2d44045 100644 --- a/public/login.html +++ b/public/login.html @@ -15,7 +15,7 @@ - +

Don't have an account? Register

diff --git a/src/app.js b/src/app.js index 5dd8b96..30e7567 100644 --- a/src/app.js +++ b/src/app.js @@ -45,6 +45,9 @@ function createApp() { res.json({ ok: true }); }); + // Root: send visitors to the login page (there is no index.html) + app.get('/', (_req, res) => res.redirect('/login.html')); + // Auth routes (public) app.post('/api/auth/register', registerLimiter, authRoutes.postRegister); app.post('/api/auth/login', loginLimiter, authRoutes.postLogin); diff --git a/src/routes/auth.js b/src/routes/auth.js index ec917c2..d05f794 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -2,6 +2,11 @@ const rateLimit = require('express-rate-limit'); const zod = require('zod'); const { AppError } = require('../utils/errors'); const { register, verifyLogin } = require('../services/authService'); +const { getDb } = require('../db'); + +function userHasHero(userId) { + return !!getDb().prepare('SELECT 1 FROM characters WHERE user_id = ?').get(userId); +} // Validation schemas const registerSchema = zod.strictObject({ @@ -63,7 +68,7 @@ async function handleLogin(req, res) { req.session.regenerate((err) => { if (err) throw new AppError('INTERNAL_ERROR', 'Session regeneration failed.', 500); req.session.userId = user.id; - res.json({ ok: true, data: { user: { id: user.id, username: user.username }, hasHero: false } }); + res.json({ ok: true, data: { user: { id: user.id, username: user.username }, hasHero: userHasHero(user.id) } }); }); } @@ -77,7 +82,7 @@ async function handleLogout(req, res) { async function handleMe(req, res) { const userId = req.session.userId; - res.json({ ok: true, data: { user: { id: userId, username: req.session.username || '' }, hasHero: false } }); + res.json({ ok: true, data: { user: { id: userId, username: req.session.username || '' }, hasHero: userHasHero(userId) } }); } module.exports = {