114 lines
4.1 KiB
Go
114 lines
4.1 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apispec"
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/envelope"
|
|
reviewsvc "github.com/aoagents/agent-orchestrator/backend/internal/service/review"
|
|
)
|
|
|
|
// ListReviewsResponse is the body of GET /api/v1/sessions/{sessionId}/reviews.
|
|
// reviewerHandleId is the live reviewer pane's runtime handle, for the UI to
|
|
// attach its terminal over /mux (empty when no reviewer has run).
|
|
type ListReviewsResponse struct {
|
|
ReviewerHandleID string `json:"reviewerHandleId"`
|
|
Reviews []domain.ReviewRun `json:"reviews"`
|
|
}
|
|
|
|
// ReviewRunResponse is the body of trigger (200/201) and submit (200). It
|
|
// carries the run plus the reviewer pane handle so the UI can attach a terminal.
|
|
type ReviewRunResponse struct {
|
|
Review domain.ReviewRun `json:"review"`
|
|
ReviewerHandleID string `json:"reviewerHandleId"`
|
|
}
|
|
|
|
// SubmitReviewInput is the body of POST /api/v1/sessions/{sessionId}/reviews/submit.
|
|
type SubmitReviewInput struct {
|
|
RunID string `json:"runId" description:"Review run id being completed."`
|
|
Verdict string `json:"verdict" description:"Review verdict: approved or changes_requested."`
|
|
Body string `json:"body" description:"Review body recorded by AO. Required for changes_requested."`
|
|
}
|
|
|
|
// ReviewsController owns the session-scoped /reviews routes. A nil Svc returns 501.
|
|
type ReviewsController struct {
|
|
Svc reviewsvc.Manager
|
|
}
|
|
|
|
// Register mounts the review routes on the supplied router.
|
|
func (c *ReviewsController) Register(r chi.Router) {
|
|
r.Get("/sessions/{sessionId}/reviews", c.list)
|
|
r.Post("/sessions/{sessionId}/reviews/trigger", c.trigger)
|
|
r.Post("/sessions/{sessionId}/reviews/submit", c.submit)
|
|
}
|
|
|
|
func (c *ReviewsController) list(w http.ResponseWriter, r *http.Request) {
|
|
if c.Svc == nil {
|
|
apispec.NotImplemented(w, r, "GET", "/api/v1/sessions/{sessionId}/reviews")
|
|
return
|
|
}
|
|
res, err := c.Svc.List(r.Context(), sessionID(r))
|
|
if err != nil {
|
|
writeReviewError(w, r, err)
|
|
return
|
|
}
|
|
runs := res.Runs
|
|
if runs == nil {
|
|
runs = []domain.ReviewRun{}
|
|
}
|
|
envelope.WriteJSON(w, http.StatusOK, ListReviewsResponse{ReviewerHandleID: res.ReviewerHandleID, Reviews: runs})
|
|
}
|
|
|
|
func (c *ReviewsController) trigger(w http.ResponseWriter, r *http.Request) {
|
|
if c.Svc == nil {
|
|
apispec.NotImplemented(w, r, "POST", "/api/v1/sessions/{sessionId}/reviews/trigger")
|
|
return
|
|
}
|
|
res, err := c.Svc.Trigger(r.Context(), sessionID(r))
|
|
if err != nil {
|
|
writeReviewError(w, r, err)
|
|
return
|
|
}
|
|
// 201 when a new pass was started; 200 when an existing run for the same
|
|
// commit was reused.
|
|
status := http.StatusOK
|
|
if res.Created {
|
|
status = http.StatusCreated
|
|
}
|
|
envelope.WriteJSON(w, status, ReviewRunResponse{Review: res.Run, ReviewerHandleID: res.ReviewerHandleID})
|
|
}
|
|
|
|
func (c *ReviewsController) submit(w http.ResponseWriter, r *http.Request) {
|
|
if c.Svc == nil {
|
|
apispec.NotImplemented(w, r, "POST", "/api/v1/sessions/{sessionId}/reviews/submit")
|
|
return
|
|
}
|
|
var in SubmitReviewInput
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
envelope.WriteAPIError(w, r, http.StatusBadRequest, "bad_request", "INVALID_BODY", "Invalid request body", nil)
|
|
return
|
|
}
|
|
run, err := c.Svc.Submit(r.Context(), sessionID(r), in.RunID, domain.ReviewVerdict(in.Verdict), in.Body)
|
|
if err != nil {
|
|
writeReviewError(w, r, err)
|
|
return
|
|
}
|
|
envelope.WriteJSON(w, http.StatusOK, ReviewRunResponse{Review: run})
|
|
}
|
|
|
|
func writeReviewError(w http.ResponseWriter, r *http.Request, err error) {
|
|
switch {
|
|
case errors.Is(err, reviewsvc.ErrInvalid):
|
|
envelope.WriteAPIError(w, r, http.StatusUnprocessableEntity, "unprocessable", "REVIEW_INVALID", err.Error(), nil)
|
|
case errors.Is(err, reviewsvc.ErrNotFound):
|
|
envelope.WriteAPIError(w, r, http.StatusNotFound, "not_found", "REVIEW_NOT_FOUND", err.Error(), nil)
|
|
default:
|
|
envelope.WriteAPIError(w, r, http.StatusInternalServerError, "internal", "REVIEW_OPERATION_FAILED", "Review operation failed", nil)
|
|
}
|
|
}
|