package aioemodule import ( "encoding/json" "errors" "io" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/missionworkforce/missionmatch/internal/platform/security" ) // Handler wires the AIOE HTTP surface. Outcome capture is a recruiter grade write (the one click // screening feedback loop); everything under /aioe is admin only, internal proof the engine is // alive, never customer marketing. type Handler struct { svc *Service } func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} } func (h *Handler) Routes(r chi.Router) { r.With(security.RequireCapability(security.CapCandidateWrite)).Post("/outcomes", h.postOutcome) r.Route("/aioe", func(r chi.Router) { r.Use(security.RequireRole(security.RoleAdmin)) r.Get("/status", h.getStatus) r.Get("/history", h.getHistory) r.Post("/run", h.postRun) r.Post("/promote", h.postPromote) }) } func (h *Handler) postOutcome(w http.ResponseWriter, r *http.Request) { var req PostOutcomeRequest if err := json.NewDecoder(io.LimitReader(r.Body, 64*1024)).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid json body") return } out, err := h.svc.RecordOutcome(r.Context(), req) if err != nil { writeServiceErr(w, err) return } writeJSON(w, http.StatusCreated, out) } func (h *Handler) getStatus(w http.ResponseWriter, r *http.Request) { out, err := h.svc.Status(r.Context()) if err != nil { writeServiceErr(w, err) return } writeJSON(w, http.StatusOK, out) } func (h *Handler) getHistory(w http.ResponseWriter, r *http.Request) { limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) out, err := h.svc.History(r.Context(), limit) if err != nil { writeServiceErr(w, err) return } writeJSON(w, http.StatusOK, map[string]any{"adjustments": out}) } func (h *Handler) postRun(w http.ResponseWriter, r *http.Request) { report, err := h.svc.RunNow(r.Context()) if err != nil { writeServiceErr(w, err) return } writeJSON(w, http.StatusOK, report) } func (h *Handler) postPromote(w http.ResponseWriter, r *http.Request) { report, err := h.svc.Promote(r.Context()) if err != nil { writeServiceErr(w, err) return } writeJSON(w, http.StatusOK, report) } func writeServiceErr(w http.ResponseWriter, err error) { switch { case errors.Is(err, ErrBadRequest): writeError(w, http.StatusBadRequest, err.Error()) case errors.Is(err, ErrNotConfigured): writeError(w, http.StatusServiceUnavailable, "the optimization engine is not configured on this deployment") case errors.Is(err, ErrNoDatabase): writeError(w, http.StatusServiceUnavailable, "no database configured") default: writeError(w, http.StatusInternalServerError, "internal error") } } func writeError(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) }