package handlers import ( "encoding/json" "errors" "net/http" "github.com/google/uuid" "mgit.msbls.de/m/paliad/internal/services" lp "mgit.msbls.de/m/paliad/pkg/litigationplanner" ) // Slice D (m/paliad#124 §5, mig 145) — REST endpoints for paliad.scenarios. // // Routes (registered in handlers.go): // // GET /api/scenarios?project= — list project's scenarios // GET /api/scenarios?abstract=true — list caller's abstract scenarios // GET /api/scenarios/{id} — fetch one // POST /api/scenarios — create // PATCH /api/scenarios/{id} — partial update // PUT /api/projects/{id}/active-scenario — set/clear active scenario // DELETE /api/scenarios/{id} — remove // // All endpoints require auth; visibility is enforced by // ScenarioService.requireProjectVisible / requireVisible. func requireScenarioService(w http.ResponseWriter) bool { if dbSvc == nil || dbSvc.scenario == nil { writeJSON(w, http.StatusServiceUnavailable, map[string]string{ "error": "Szenarien sind vorübergehend nicht verfügbar (keine Datenbank).", }) return false } return true } // scenarioErrorToStatus maps service errors to HTTP statuses. Mirrors // the patterns in projects.go and event_choices.go. func scenarioErrorToStatus(err error) (int, string) { switch { case errors.Is(err, lp.ErrUnknownScenario), errors.Is(err, services.ErrScenarioNotVisible): return http.StatusNotFound, "Szenario nicht gefunden" case errors.Is(err, services.ErrInvalidInput), errors.Is(err, lp.ErrInvalidScenario), errors.Is(err, lp.ErrScenarioNoPrimary): return http.StatusBadRequest, err.Error() } return http.StatusInternalServerError, err.Error() } // handleScenariosList — GET /api/scenarios?project= OR ?abstract=true. func handleScenariosList(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } abstract := r.URL.Query().Get("abstract") == "true" projectStr := r.URL.Query().Get("project") switch { case abstract: out, err := dbSvc.scenario.ListAbstractForUser(r.Context(), uid) if err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } writeJSON(w, http.StatusOK, out) case projectStr != "": pid, err := uuid.Parse(projectStr) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige project ID"}) return } out, err := dbSvc.scenario.ListForProject(r.Context(), uid, pid) if err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } writeJSON(w, http.StatusOK, out) default: writeJSON(w, http.StatusBadRequest, map[string]string{ "error": "?project= oder ?abstract=true erforderlich", }) } } // handleScenarioGet — GET /api/scenarios/{id}. func handleScenarioGet(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } id, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige ID"}) return } out, err := dbSvc.scenario.Get(r.Context(), uid, id) if err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } writeJSON(w, http.StatusOK, out) } // handleScenarioCreate — POST /api/scenarios. func handleScenarioCreate(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } var input services.CreateScenarioInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige Anfrage"}) return } out, err := dbSvc.scenario.Create(r.Context(), uid, input) if err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } writeJSON(w, http.StatusCreated, out) } // handleScenarioPatch — PATCH /api/scenarios/{id}. func handleScenarioPatch(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } id, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige ID"}) return } var input services.PatchScenarioInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige Anfrage"}) return } out, err := dbSvc.scenario.Patch(r.Context(), uid, id, input) if err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } writeJSON(w, http.StatusOK, out) } // handleScenarioDelete — DELETE /api/scenarios/{id}. func handleScenarioDelete(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } id, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige ID"}) return } if err := dbSvc.scenario.Delete(r.Context(), uid, id); err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } w.WriteHeader(http.StatusNoContent) } // handleSetActiveScenario — PUT /api/projects/{id}/active-scenario. // Body: {"scenario_id": ""} or {"scenario_id": null} to clear. func handleSetActiveScenario(w http.ResponseWriter, r *http.Request) { if !requireScenarioService(w) { return } uid, ok := requireUser(w, r) if !ok { return } pid, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige project ID"}) return } var body struct { ScenarioID *uuid.UUID `json:"scenario_id"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ungültige Anfrage"}) return } if err := dbSvc.scenario.SetActive(r.Context(), uid, pid, body.ScenarioID); err != nil { status, msg := scenarioErrorToStatus(err) writeJSON(w, status, map[string]string{"error": msg}) return } w.WriteHeader(http.StatusNoContent) }