feat: interaction / event logging

New `events` table with (user_id, kind, created_at, data JSON).
Instruments:

Bot:
- cmd.start, cmd.history, cmd.stats, cmd.delete, cmd.export, cmd.feedback
- workout.save (source=text), workout.delete (source=bot)

Server:
- workout.save (source=webapp), workout.update, workout.delete (source=webapp)
- POST /api/events for Mini App client-side events

Mini App:
- miniapp.open on init()
- set.add on addSet(), with exercise name / reps / weight
  (per-set timestamps unlock the rest-timer feature later)

log_event swallows failures so it can never break a caller.
get_events supports user_id / kind filtering for inspection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Danny 2026-04-19 14:03:42 +02:00
parent 1d3e7d5e80
commit 52277e99de
5 changed files with 187 additions and 2 deletions

View file

@ -29,6 +29,24 @@ async function api(method, path, body = null) {
return res.json();
}
// ── Event logging (fire-and-forget) ─────────────────────────────
function logEvent(kind, data) {
if (!userId) return;
try {
fetch(API + "/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Telegram-Init-Data": tg.initData,
},
body: JSON.stringify({ kind, data: data || null }),
keepalive: true,
}).catch(() => {});
} catch (e) {
// Never let logging break anything
}
}
// ── Toast ───────────────────────────────────────────────────────
function showToast(msg) {
let toast = document.querySelector(".toast");
@ -330,6 +348,12 @@ function addSet() {
addSetToDOM(reps, weight);
syncEditorUI();
logEvent("set.add", {
exercise: currentExercise?.name || null,
reps,
weight_kg: weight,
});
repsInput.value = "";
weightInput.value = weight ? String(weight) : "";
repsInput.focus();
@ -784,6 +808,7 @@ async function loadVersion() {
async function init() {
loadVersion();
if (!userId) return;
logEvent("miniapp.open");
try {
const data = await api("GET", "/exercises");
knownExercises = data.exercises || [];