feat(admin): Knöpfe "Statistik zurücksetzen" + "Erinnerungen löschen" pro Nutzer

DELETE /api/admin/users/{id}/stats (usage/web_search_usage/cost_usage) und
.../memories (alle Erinnerungen) — require_admin, audit-geloggt; Konto, Verlauf
und (jeweils) die andere Datenart bleiben. Store-Methoden delete_user_stats/
delete_user_memories + zwei UI-Buttons je Nutzerkarte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 14:29:42 +02:00
commit 8fb5e42e1b
3 changed files with 61 additions and 0 deletions

View file

@ -147,6 +147,15 @@ class Store(ABC):
"""Loescht NUR den Gespraechsverlauf (Sessions + Nachrichten) eines Nutzers;
Konto, Erinnerungen und Nutzungsdaten bleiben. Liefert die Zahl geloeschter Nachrichten."""
@abstractmethod
def delete_user_stats(self, user_id: str) -> int:
"""Loescht die Statistik-/Nutzungsdaten (usage, web_search_usage, cost_usage)
eines Nutzers. Konto, Verlauf und Erinnerungen bleiben."""
@abstractmethod
def delete_user_memories(self, user_id: str) -> int:
"""Loescht alle Erinnerungen eines Nutzers. Liefert die Zahl geloeschter Erinnerungen."""
@abstractmethod
def reset_token(self, user_id: str) -> tuple[User, str] | None:
"""Generiert einen neuen Token fuer den Nutzer; der alte wird sofort ungueltig.
@ -585,6 +594,19 @@ class SQLiteStore(Store):
conn.execute("DELETE FROM sessions WHERE user_id = ?", (user_id,))
return n
def delete_user_stats(self, user_id: str) -> int:
with self._connect() as conn:
n = 0
for tbl in ("usage", "web_search_usage", "cost_usage"):
cur = conn.execute(f"DELETE FROM {tbl} WHERE user_id = ?", (user_id,))
n += cur.rowcount
return n
def delete_user_memories(self, user_id: str) -> int:
with self._connect() as conn:
cur = conn.execute("DELETE FROM memories WHERE user_id = ?", (user_id,))
return cur.rowcount
def reset_token(self, user_id: str) -> tuple[User, str] | None:
with self._connect() as conn:
row = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()