From 337cbb8ade155dacf598c92ceba8456efe98a3ca Mon Sep 17 00:00:00 2001 From: Omair Saleh Date: Tue, 10 Mar 2026 07:19:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20remaining=20audit=20items=20=E2=80=94=20?= =?UTF-8?q?anim=20counter,=20API=20404s,=20graceful=20shutdown,=20null=20c?= =?UTF-8?q?hecks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - anim(): guarantee final frame shows exact target value (not target-1) - API: /api/* catch-all returns 404 JSON instead of SPA fallback HTML - Server: SIGTERM handler closes pool + server gracefully - renderTeamBar: null-check allBtn before DOM manipulation --- html/live/index.html | 8 +++++--- server.js | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/html/live/index.html b/html/live/index.html index d48ee17..83d9e74 100644 --- a/html/live/index.html +++ b/html/live/index.html @@ -403,8 +403,10 @@ function renderTeamBar(stats) { // Clear existing member pills (keep the "Everyone" button) const allBtn = bar.querySelector('[data-member="all"]'); bar.innerHTML = ''; - if (activeMember === 'all') allBtn.classList.add('active'); else allBtn.classList.remove('active'); - bar.appendChild(allBtn); + if (allBtn) { + if (activeMember === 'all') allBtn.classList.add('active'); else allBtn.classList.remove('active'); + bar.appendChild(allBtn); + } stats.members.forEach(m => { const btn = document.createElement('button'); @@ -419,7 +421,7 @@ function renderTeamBar(stats) { function fmtDate(str){try{const d=new Date(str+'T00:00:00');return d.toLocaleDateString('en-GB',{weekday:'short',day:'numeric',month:'short',year:'numeric'});}catch{return str;}} function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML;} -function anim(el,target,dur){if(!el)return;const t0=performance.now();(function f(now){const p=Math.min((now-t0)/dur,1);el.textContent=Math.floor(target*(1-Math.pow(1-p,3)));if(p<1)requestAnimationFrame(f);})(t0);} +function anim(el,target,dur){if(!el)return;const t0=performance.now();(function f(now){const p=Math.min((now-t0)/dur,1);el.textContent=p>=1?target:Math.floor(target*(1-Math.pow(1-p,3)));if(p<1)requestAnimationFrame(f);})(t0);} document.querySelector('.lv-pill[data-filter="all"]').onclick=()=>filterTo('all'); init(); diff --git a/server.js b/server.js index 1508e57..9ee722b 100644 --- a/server.js +++ b/server.js @@ -233,12 +233,26 @@ app.get('/work/', (req, res) => res.redirect(301, '/work/charityright/')); // Serve static files from html/ directory app.use(express.static(path.join(__dirname, 'html'))); +// API 404 — don't let SPA fallback swallow bad API routes +app.all('/api/*', (req, res) => { + res.status(404).json({ error: 'not found' }); +}); + // SPA fallback: send index.html for unmatched routes app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'html', 'index.html')); }); const PORT = process.env.PORT || 3000; -app.listen(PORT, '0.0.0.0', () => { +const server = app.listen(PORT, '0.0.0.0', () => { console.log(`QuikCue Site server listening on :${PORT}`); }); + +// Graceful shutdown +process.on('SIGTERM', () => { + console.log('SIGTERM received, shutting down...'); + server.close(() => { + if (pool) pool.end(); + process.exit(0); + }); +});