fix: codebase audit fixes — indexes, daily_rate, anomaly key, CSRF, chat pruning, shopping_name

## v1.7.6

- DB: fix shopping_name Pi→Piadina, Grana→Formaggio, Prosciutto cotto→Affettato, Panna acida→Panna
- DB: composite indexes idx_transactions_type_date + idx_transactions_pid_type_undone (+ migration)
- PHP: daily_rate uses first_in→last_activity window (not first_in→now)
- PHP: anomaly dismiss key uses product_id+direction (stable, not product_id+round(expected))
- PHP: smart shopping — products exhausted within 14 days bypass token/family suppression
- PHP: chat pruning — DELETE messages beyond 200 after each chatSave()
- PHP: getStats() — 5 queries → 1 consolidated query with subselects
- PHP: bringCleanupObsolete — 300ms delay between bulk removals
- PHP: CSRF guard — POST write actions require X-EverShelf-Request:1 or Content-Type:application/json
- JS: api() — sends X-EverShelf-Request:1 on all POST requests
- JS: _opLog — prunes entries older than 30 days in addition to 200-entry cap
This commit is contained in:
dadaloop82
2026-05-10 11:26:10 +00:00
parent f65fb4365c
commit ed447d5811
6 changed files with 106 additions and 20 deletions
+8 -5
View File
@@ -2502,7 +2502,7 @@ async function api(action, params = {}, method = 'GET', body = null, extraHeader
}
const opts = { method, cache: 'no-store' };
if (body) {
opts.headers = { 'Content-Type': 'application/json', ...extraHeaders };
opts.headers = { 'Content-Type': 'application/json', 'X-EverShelf-Request': '1', ...extraHeaders };
opts.body = JSON.stringify(body);
} else if (Object.keys(extraHeaders).length > 0) {
opts.headers = { ...extraHeaders };
@@ -9264,10 +9264,13 @@ async function cleanupObsoleteBringItems() {
function logOperation(action, details) {
try {
const log = JSON.parse(localStorage.getItem('_opLog') || '[]');
log.push({ ts: new Date().toISOString(), action, details });
// Keep last 200 entries
if (log.length > 200) log.splice(0, log.length - 200);
localStorage.setItem('_opLog', JSON.stringify(log));
const now = Date.now();
log.push({ ts: new Date(now).toISOString(), action, details });
// Prune: keep only last 200 entries AND entries newer than 30 days
const cutoff = now - 30 * 24 * 60 * 60 * 1000;
const pruned = log.filter(e => new Date(e.ts).getTime() >= cutoff);
const final = pruned.length > 200 ? pruned.slice(pruned.length - 200) : pruned;
localStorage.setItem('_opLog', JSON.stringify(final));
} catch (e) { /* ignore */ }
}