""" Workflow Questions Router (Phase 1) CRUD für workflow_question_catalog Tabelle. Endpunkte: - GET /api/workflow/questions - Liste aller Fragen - GET /api/workflow/questions/{id} - Einzelne Frage - POST /api/workflow/questions - Neue Frage (Admin only) - PUT /api/workflow/questions/{id} - Frage aktualisieren (Admin only) - DELETE /api/workflow/questions/{id} - Frage löschen (Admin only) """ from fastapi import APIRouter, Depends, HTTPException from typing import List, Optional from auth import require_auth, require_admin from db import get_db, get_cursor, r2d from workflow_models import QuestionCatalogEntry from pydantic import BaseModel router = APIRouter() class QuestionCatalogCreate(BaseModel): """Request-Modell für neue Frage""" question_type: str label: str question_template: str answer_spectrum: List[str] normalization_rules: Optional[dict] = None class QuestionCatalogUpdate(BaseModel): """Request-Modell für Frage-Update""" label: Optional[str] = None question_template: Optional[str] = None answer_spectrum: Optional[List[str]] = None normalization_rules: Optional[dict] = None active: Optional[bool] = None @router.get("/api/workflow/questions") def list_questions( active_only: bool = True, session: dict = Depends(require_auth) ): """ Liste alle Fragen aus dem Katalog. Query-Parameter: - active_only: Nur aktive Fragen (default: true) """ with get_db() as conn: cur = get_cursor(conn) if active_only: cur.execute( """SELECT id, question_type, label, question_template, answer_spectrum, normalization_rules, active, created_at::text as created_at FROM workflow_question_catalog WHERE active = true ORDER BY question_type""" ) else: cur.execute( """SELECT id, question_type, label, question_template, answer_spectrum, normalization_rules, active, created_at::text as created_at FROM workflow_question_catalog ORDER BY question_type""" ) rows = cur.fetchall() return [r2d(row) for row in rows] @router.get("/api/workflow/questions/{question_id}") def get_question( question_id: str, session: dict = Depends(require_auth) ): """Einzelne Frage abrufen""" with get_db() as conn: cur = get_cursor(conn) cur.execute( """SELECT id, question_type, label, question_template, answer_spectrum, normalization_rules, active, created_at::text as created_at FROM workflow_question_catalog WHERE id = %s""", (question_id,) ) row = cur.fetchone() if not row: raise HTTPException(404, f"Frage nicht gefunden: {question_id}") return r2d(row) @router.post("/api/workflow/questions") def create_question( data: QuestionCatalogCreate, session: dict = Depends(require_admin) ): """ Neue Frage erstellen (Admin only). Validierungen: - question_type muss eindeutig sein - answer_spectrum muss mindestens 2 Werte enthalten """ # Validierung if len(data.answer_spectrum) < 2: raise HTTPException(400, "answer_spectrum muss mindestens 2 Werte enthalten") with get_db() as conn: cur = get_cursor(conn) # Prüfe ob question_type bereits existiert cur.execute( "SELECT id FROM workflow_question_catalog WHERE question_type = %s", (data.question_type,) ) if cur.fetchone(): raise HTTPException(400, f"question_type '{data.question_type}' existiert bereits") # Erstelle Frage cur.execute( """INSERT INTO workflow_question_catalog (question_type, label, question_template, answer_spectrum, normalization_rules, active) VALUES (%s, %s, %s, %s, %s, true) RETURNING id, created_at::text as created_at""", ( data.question_type, data.label, data.question_template, json.dumps(data.answer_spectrum), json.dumps(data.normalization_rules) if data.normalization_rules else None ) ) result = cur.fetchone() conn.commit() return { "id": result[0], "question_type": data.question_type, "label": data.label, "question_template": data.question_template, "answer_spectrum": data.answer_spectrum, "normalization_rules": data.normalization_rules, "active": True, "created_at": result[1] } @router.put("/api/workflow/questions/{question_id}") def update_question( question_id: str, data: QuestionCatalogUpdate, session: dict = Depends(require_admin) ): """Frage aktualisieren (Admin only)""" import json # Prüfe ob Frage existiert with get_db() as conn: cur = get_cursor(conn) cur.execute("SELECT id FROM workflow_question_catalog WHERE id = %s", (question_id,)) if not cur.fetchone(): raise HTTPException(404, f"Frage nicht gefunden: {question_id}") # Build UPDATE statement dynamically updates = [] params = [] if data.label is not None: updates.append("label = %s") params.append(data.label) if data.question_template is not None: updates.append("question_template = %s") params.append(data.question_template) if data.answer_spectrum is not None: if len(data.answer_spectrum) < 2: raise HTTPException(400, "answer_spectrum muss mindestens 2 Werte enthalten") updates.append("answer_spectrum = %s") params.append(json.dumps(data.answer_spectrum)) if data.normalization_rules is not None: updates.append("normalization_rules = %s") params.append(json.dumps(data.normalization_rules)) if data.active is not None: updates.append("active = %s") params.append(data.active) if not updates: raise HTTPException(400, "Keine Aktualisierungen angegeben") params.append(question_id) update_sql = f"UPDATE workflow_question_catalog SET {', '.join(updates)} WHERE id = %s" cur.execute(update_sql, tuple(params)) conn.commit() # Return updated question return get_question(question_id, session) @router.delete("/api/workflow/questions/{question_id}") def delete_question( question_id: str, session: dict = Depends(require_admin) ): """ Frage löschen (Admin only). Setzt active=false statt physischem Löschen (Soft Delete). """ with get_db() as conn: cur = get_cursor(conn) cur.execute( "UPDATE workflow_question_catalog SET active = false WHERE id = %s RETURNING id", (question_id,) ) result = cur.fetchone() if not result: raise HTTPException(404, f"Frage nicht gefunden: {question_id}") conn.commit() return {"status": "deleted", "id": question_id}