You've already forked agentic-coding-workflow
219 lines
5.6 KiB
Go
219 lines
5.6 KiB
Go
// 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))
|
|
}
|