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:
parent
f83c04f3d6
commit
8fb5e42e1b
3 changed files with 61 additions and 0 deletions
|
|
@ -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.
|
||||
|
|
|
|||
22
app/store.py
22
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()
|
||||
|
|
|
|||
|
|
@ -1198,6 +1198,8 @@ function buildUserCard(u) {
|
|||
<button class="btn-admin text-xs rounded-lg border px-2.5 py-1.5 transition-colors"></button>
|
||||
<button class="btn-token text-xs rounded-lg border border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-300 px-2.5 py-1.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 transition-colors">Link erneuern</button>
|
||||
<button class="btn-history text-xs rounded-lg border border-orange-300 dark:border-orange-700 text-orange-700 dark:text-orange-300 px-2.5 py-1.5 hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-colors">Verlauf löschen</button>
|
||||
<button class="btn-stats text-xs rounded-lg border border-orange-300 dark:border-orange-700 text-orange-700 dark:text-orange-300 px-2.5 py-1.5 hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-colors">Statistik zurücksetzen</button>
|
||||
<button class="btn-mem-clear text-xs rounded-lg border border-orange-300 dark:border-orange-700 text-orange-700 dark:text-orange-300 px-2.5 py-1.5 hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-colors">Erinnerungen löschen</button>
|
||||
<button class="btn-delete text-xs rounded-lg border border-red-300 dark:border-red-800 text-red-600 dark:text-red-400 px-2.5 py-1.5 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue