You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
// handler.go exposes the change-request HTTP endpoints under middleware.Auth,
|
||||
// mirroring workspace/handler.go. Routes:
|
||||
//
|
||||
// POST /api/v1/workspaces/{wsID}/change-requests create
|
||||
// GET /api/v1/workspaces/{wsID}/change-requests list
|
||||
// GET /api/v1/change-requests/{id} get
|
||||
// POST /api/v1/change-requests/{id}/review review (human approve/reject)
|
||||
// POST /api/v1/change-requests/{id}/merge gated merge
|
||||
// POST /api/v1/change-requests/{id}/sync refresh status from host
|
||||
package changerequest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
||||
httpx "github.com/yan1h/agent-coding-workflow/internal/transport/http"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
|
||||
)
|
||||
|
||||
// AdminLookup resolves is_admin for a user (narrow interface, same as
|
||||
// workspace.AdminLookup so the userAdminAdapter can be reused).
|
||||
type AdminLookup interface {
|
||||
IsAdmin(ctx context.Context, id uuid.UUID) (bool, error)
|
||||
}
|
||||
|
||||
// Handler holds the change-request service + auth deps.
|
||||
type Handler struct {
|
||||
svc Service
|
||||
resolver middleware.SessionResolver
|
||||
users AdminLookup
|
||||
}
|
||||
|
||||
// NewHandler constructs the Handler.
|
||||
func NewHandler(svc Service, resolver middleware.SessionResolver, users AdminLookup) *Handler {
|
||||
return &Handler{svc: svc, resolver: resolver, users: users}
|
||||
}
|
||||
|
||||
// Mount registers the change-request routes on r under the Auth middleware.
|
||||
func (h *Handler) Mount(r chi.Router) {
|
||||
auth := middleware.Auth(h.resolver, middleware.AuthOptions{})
|
||||
|
||||
r.With(auth).Route("/api/v1/workspaces/{wsID}/change-requests", func(r chi.Router) {
|
||||
r.Post("/", h.create)
|
||||
r.Get("/", h.list)
|
||||
})
|
||||
r.With(auth).Route("/api/v1/change-requests/{id}", func(r chi.Router) {
|
||||
r.Get("/", h.get)
|
||||
r.Post("/review", h.review)
|
||||
r.Post("/merge", h.merge)
|
||||
r.Post("/sync", h.sync)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) caller(r *http.Request) (Caller, error) {
|
||||
uid, ok := middleware.UserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
return Caller{}, errs.New(errs.CodeUnauthorized, "unauthenticated")
|
||||
}
|
||||
isAdmin, err := h.users.IsAdmin(r.Context(), uid)
|
||||
if err != nil {
|
||||
return Caller{}, err
|
||||
}
|
||||
return Caller{UserID: uid, IsAdmin: isAdmin}, nil
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) { httpx.WriteJSON(w, status, body) }
|
||||
|
||||
func writeErr(w http.ResponseWriter, r *http.Request, err error) {
|
||||
httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
|
||||
}
|
||||
|
||||
func parseUUID(s string) (uuid.UUID, error) {
|
||||
id, err := uuid.Parse(s)
|
||||
if err != nil {
|
||||
return uuid.Nil, errs.Wrap(err, errs.CodeInvalidInput, "invalid uuid")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (h *Handler) create(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
wsID, err := parseUUID(chi.URLParam(r, "wsID"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
var req createReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, r, errs.Wrap(err, errs.CodeInvalidInput, "decode body"))
|
||||
return
|
||||
}
|
||||
cr, err := h.svc.Create(r.Context(), c, wsID, CreateInput{
|
||||
SourceBranch: req.SourceBranch, TargetBranch: req.TargetBranch,
|
||||
Title: req.Title, Description: req.Description,
|
||||
RequirementID: req.RequirementID, IssueID: req.IssueID,
|
||||
})
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, toResp(cr))
|
||||
}
|
||||
|
||||
func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
wsID, err := parseUUID(chi.URLParam(r, "wsID"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
out, err := h.svc.ListByWorkspace(r.Context(), c, wsID)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
resp := make([]changeRequestResp, 0, len(out))
|
||||
for _, cr := range out {
|
||||
resp = append(resp, toResp(cr))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
id, err := parseUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
cr, err := h.svc.Get(r.Context(), c, id)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, toResp(cr))
|
||||
}
|
||||
|
||||
func (h *Handler) review(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
id, err := parseUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
var req reviewReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, r, errs.Wrap(err, errs.CodeInvalidInput, "decode body"))
|
||||
return
|
||||
}
|
||||
cr, err := h.svc.Review(r.Context(), c, id, Verdict(req.Verdict), req.Note)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, toResp(cr))
|
||||
}
|
||||
|
||||
func (h *Handler) merge(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
id, err := parseUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
var req mergeReq
|
||||
// merge body is optional (method default)
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
cr, err := h.svc.Merge(r.Context(), c, id, req.Method)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, toResp(cr))
|
||||
}
|
||||
|
||||
func (h *Handler) sync(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := h.caller(r)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
id, err := parseUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
cr, err := h.svc.SyncStatus(r.Context(), c, id)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, toResp(cr))
|
||||
}
|
||||
Reference in New Issue
Block a user