diff --git a/app/api/admin.py b/app/api/admin.py
index 51642d4..cab3535 100644
--- a/app/api/admin.py
+++ b/app/api/admin.py
@@ -131,6 +131,18 @@ async def delete_user(user_id: str):
return {"deleted": user_id}
+@router.delete("/admin/users/{user_id}/history", dependencies=[Depends(require_admin)])
+async def delete_user_history(user_id: str, request: Request):
+ """Loescht NUR den Gespraechsverlauf (Sessions + Nachrichten) eines Nutzers;
+ Konto, Erinnerungen und Nutzungsdaten bleiben erhalten."""
+ 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_history(user_id)
+ log_admin_action(request, "user_history_deleted", user_id=user_id)
+ return {"user_id": user_id, "deleted_messages": 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 c3252e7..5194ef3 100644
--- a/app/store.py
+++ b/app/store.py
@@ -126,6 +126,11 @@ class Store(ABC):
Nutzungsdaten). Anonymer Nutzer kann nicht geloescht werden.
Liefert True, wenn der Nutzer existierte und geloescht wurde."""
+ @abstractmethod
+ def delete_user_history(self, user_id: str) -> int:
+ """Loescht NUR den Gespraechsverlauf (Sessions + Nachrichten) eines Nutzers;
+ Konto, Erinnerungen und Nutzungsdaten bleiben. Liefert die Zahl geloeschter Nachrichten."""
+
@abstractmethod
def reset_token(self, user_id: str) -> tuple[User, str] | None:
"""Generiert einen neuen Token fuer den Nutzer; der alte wird sofort ungueltig.
@@ -492,6 +497,17 @@ class SQLiteStore(Store):
conn.execute("DELETE FROM users WHERE id = ?", (user_id,))
return True
+ def delete_user_history(self, user_id: str) -> int:
+ with self._connect() as conn:
+ cur = conn.execute(
+ "DELETE FROM messages WHERE session_id IN"
+ " (SELECT id FROM sessions WHERE user_id = ?)",
+ (user_id,),
+ )
+ n = cur.rowcount
+ conn.execute("DELETE FROM sessions WHERE user_id = ?", (user_id,))
+ return n
+
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 f449547..016288d 100644
--- a/app/web/app.js
+++ b/app/web/app.js
@@ -1193,6 +1193,7 @@ function buildUserCard(u) {
+
@@ -1409,6 +1410,13 @@ function buildUserCard(u) {
}
});
+ // Verlauf löschen (Konto + Erinnerungen bleiben)
+ card.querySelector(".btn-history").addEventListener("click", async () => {
+ if (!confirm(`Gesprächsverlauf von „${u.display_name}" löschen?\nKonto und Erinnerungen bleiben erhalten.`)) return;
+ const res = await adminFetch(`/api/admin/users/${uid}/history`, "DELETE");
+ if (res) alert(`Verlauf gelöscht (${res.deleted_messages ?? 0} Nachrichten).`);
+ });
+
// Löschen
card.querySelector(".btn-delete").addEventListener("click", async () => {
if (!confirm(`Nutzer „${u.display_name}" und alle Daten unwiderruflich löschen?`)) return;