diff --git a/app/api/admin.py b/app/api/admin.py
index 95a47f6..dadea8a 100644
--- a/app/api/admin.py
+++ b/app/api/admin.py
@@ -143,6 +143,29 @@ async def delete_user_history(user_id: str, request: Request):
return {"user_id": user_id, "deleted_messages": deleted}
+@router.delete("/admin/users/{user_id}/stats", dependencies=[Depends(require_admin)])
+async def delete_user_stats(user_id: str, request: Request):
+ """Setzt die Statistik-/Nutzungsdaten (Anfragen, Web-Suchen, Kosten) eines
+ Nutzers zurueck; Konto, Verlauf und Erinnerungen bleiben."""
+ store = get_store()
+ if store.get_user(user_id) is None:
+ raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
+ deleted = store.delete_user_stats(user_id)
+ log_admin_action(request, "user_stats_reset", user_id=user_id)
+ return {"user_id": user_id, "deleted_rows": deleted}
+
+
+@router.delete("/admin/users/{user_id}/memories", dependencies=[Depends(require_admin)])
+async def delete_user_memories(user_id: str, request: Request):
+ """Loescht alle Erinnerungen eines Nutzers; Konto, Verlauf und Statistik bleiben."""
+ store = get_store()
+ if store.get_user(user_id) is None:
+ raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
+ deleted = store.delete_user_memories(user_id)
+ log_admin_action(request, "user_memories_deleted", user_id=user_id)
+ return {"user_id": user_id, "deleted_memories": deleted}
+
+
@router.post("/admin/users/{user_id}/token", response_model=UserCreated, dependencies=[Depends(require_admin)])
async def reset_token(user_id: str):
"""Stellt einen neuen Bearer-Token aus; der alte wird sofort ungueltig.
diff --git a/app/store.py b/app/store.py
index 8629938..de33432 100644
--- a/app/store.py
+++ b/app/store.py
@@ -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()
diff --git a/app/web/app.js b/app/web/app.js
index 9496dc0..f487297 100644
--- a/app/web/app.js
+++ b/app/web/app.js
@@ -1198,6 +1198,8 @@ function buildUserCard(u) {
+
+
@@ -1490,6 +1492,20 @@ function buildUserCard(u) {
if (res) alert(`Verlauf gelöscht (${res.deleted_messages ?? 0} Nachrichten).`);
});
+ // Statistik zurücksetzen (Anfragen, Web-Suchen, Kosten)
+ card.querySelector(".btn-stats").addEventListener("click", async () => {
+ if (!confirm(`Statistik von „${u.display_name}" zurücksetzen?\nAnfragen, Web-Suchen und Kosten werden auf 0 gesetzt; Konto, Verlauf und Erinnerungen bleiben.`)) return;
+ const res = await adminFetch(`/api/admin/users/${uid}/stats`, "DELETE");
+ if (res) alert(`Statistik zurückgesetzt (${res.deleted_rows ?? 0} Zeilen).`);
+ });
+
+ // Erinnerungen löschen (alle)
+ card.querySelector(".btn-mem-clear").addEventListener("click", async () => {
+ if (!confirm(`Alle Erinnerungen von „${u.display_name}" löschen?\nKonto, Verlauf und Statistik bleiben.`)) return;
+ const res = await adminFetch(`/api/admin/users/${uid}/memories`, "DELETE");
+ if (res) alert(`Erinnerungen gelöscht (${res.deleted_memories ?? 0}).`);
+ });
+
// Löschen
card.querySelector(".btn-delete").addEventListener("click", async () => {
if (!confirm(`Nutzer „${u.display_name}" und alle Daten unwiderruflich löschen?`)) return;