Orphan-Junction-Cleanup + Refresh-Button für Fragen/Wörter

- Backend: DELETE question/word räumt alle Junction-Zeilen mit auf
- Backend: /purge-orphans bereinigt verwaiste Junctions per Objekt
- Frontend: reloadQW ruft purgeOrphans vor dem Neu-Laden auf
- Frontend: ↺-Button in Wörter- und Fragen-Sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 20:38:26 +02:00
parent c2a2b0e6f7
commit 8f01c0396e
3 changed files with 64 additions and 4 deletions

46
app.py
View File

@@ -1242,10 +1242,22 @@ def get_object_words_list(obj_id: str):
return jsonify({"data": items})
def _delete_junction_rows(collection: str, field: str, value: str, token: str):
"""Löscht alle Junction-Zeilen für einen gegebenen Fremdschlüssel."""
data, s = _directus("GET",
f"/items/{collection}?filter[{field}][_eq]={value}&fields=id&limit=5000", token)
ids = [e["id"] for e in (data.get("data") or []) if e.get("id")]
if ids:
_directus("DELETE", f"/items/{collection}", token, ids)
@app.route("/api/question/<q_id>", methods=["DELETE"])
def delete_question_item(q_id: str):
"""Löscht eine Frage aus Directus."""
"""Löscht eine Frage + alle zugehörigen Junction-Zeilen aus Directus."""
token = request.headers.get("Authorization", "")
_delete_junction_rows("questions_objects", "questions_id", q_id, token)
_delete_junction_rows("questions_distractor_words", "questions_id", q_id, token)
_delete_junction_rows("questions_words", "questions_id", q_id, token)
_, status = _directus("DELETE", f"/items/questions/{q_id}", token)
if status in (200, 204):
return jsonify({"ok": True})
@@ -1254,14 +1266,44 @@ def delete_question_item(q_id: str):
@app.route("/api/word/<w_id>", methods=["DELETE"])
def delete_word_item(w_id: str):
"""Löscht ein Wort aus Directus."""
"""Löscht ein Wort + alle zugehörigen Junction-Zeilen aus Directus."""
token = request.headers.get("Authorization", "")
_delete_junction_rows("words_objects", "words_id", w_id, token)
_delete_junction_rows("questions_words", "words_id", w_id, token)
_delete_junction_rows("questions_distractor_words", "words_id", w_id, token)
_, status = _directus("DELETE", f"/items/words/{w_id}", token)
if status in (200, 204):
return jsonify({"ok": True})
return jsonify({"error": "Delete failed"}), status
@app.route("/api/object/<obj_id>/purge-orphans", methods=["POST"])
def purge_orphan_junctions(obj_id: str):
"""
Bereinigt verwaiste Junction-Einträge für ein Objekt:
Entfernt Zeilen aus questions_objects/words_objects deren Frage/Wort nicht mehr existiert.
"""
token = request.headers.get("Authorization", "")
removed = 0
for junc_col, fk_field, item_col in [
("questions_objects", "questions_id", "questions"),
("words_objects", "words_id", "words"),
]:
junc_data, _ = _directus("GET",
f"/items/{junc_col}?filter[objects_id][_eq]={obj_id}&fields=id,{fk_field}&limit=5000", token)
for row in (junc_data.get("data") or []):
fk_val = row.get(fk_field)
if not fk_val:
continue
item_data, s = _directus("GET", f"/items/{item_col}/{fk_val}?fields=id", token)
if s != 200 or not item_data.get("data"):
_directus("DELETE", f"/items/{junc_col}/{row['id']}", token)
removed += 1
return jsonify({"ok": True, "orphans_removed": removed})
@app.route("/api/fix-distractor-field", methods=["POST"])
def fix_distractor_field():
"""Setzt special=m2m auf questions.distractor_words (einmalig)."""