// Package approvalapi ist die HTTP-Bindung fuer das Freigabe-Postfach // (APR-01). Die Nutzeridentitaet kommt bis zur Core-JWT-Anbindung // (Muster aus UX-10/INT-18, hier noch nicht Ticket-Scope) uebergangsweise // aus dem Header X-User-Id, analog zum DMS-Uebergangsmodus vor UX-10. package approvalapi import ( "encoding/json" "errors" "net/http" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/approval" ) type Server struct { svc *approval.Service } func NewServer(svc *approval.Service) *Server { return &Server{svc: svc} } func (s *Server) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /approvals", s.requireUser(s.handleList)) mux.HandleFunc("POST /approvals/{id}/decide", s.requireUser(s.handleDecide)) } func (s *Server) requireUser(next func(w http.ResponseWriter, r *http.Request, userID string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID := r.Header.Get("X-User-Id") if userID == "" { writeError(w, http.StatusUnauthorized, "X-User-Id fehlt") return } next(w, r, userID) } } func (s *Server) handleList(w http.ResponseWriter, r *http.Request, userID string) { tasks, err := s.svc.OpenForAssignee(r.Context(), userID) if err != nil { writeError(w, http.StatusInternalServerError, "aufgaben konnten nicht gelesen werden") return } writeJSON(w, http.StatusOK, tasks) } type decideRequest struct { Decision string `json:"decision"` Reason string `json:"reason"` } func (s *Server) handleDecide(w http.ResponseWriter, r *http.Request, userID string) { id := r.PathValue("id") var req decideRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper") return } task, err := s.svc.Decide(r.Context(), id, userID, approval.Decision(req.Decision), req.Reason) switch { case err == nil: writeJSON(w, http.StatusOK, task) case errors.Is(err, approval.ErrNotFound): writeError(w, http.StatusNotFound, "aufgabe nicht gefunden") case errors.Is(err, approval.ErrForbidden): writeError(w, http.StatusForbidden, "nicht zustaendig fuer diese aufgabe") case errors.Is(err, approval.ErrAlreadyDecided): writeError(w, http.StatusConflict, "aufgabe wurde bereits entschieden") default: writeError(w, http.StatusInternalServerError, "entscheidung konnte nicht verarbeitet werden") } } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]string{"error": message}) }