- Add auth.RequireScope() to all handler routes for proper authorization - Add SDLC OpenAPI endpoint documentation (state, features, tasks, branches, merge, archive, orchestrator) - Add SDLC documentation guides (getting-started, cli-reference, api-reference, command-catalog) - Add artifact_test.go for SDLC artifact coverage - Add CLAUDE.md rules: auth scopes requirement, error wrapping with %w - Fix error wrapping to use %w instead of %v throughout codebase - Improve CLI merge command with conflict detection and resolution - Fix handler tests to include auth middleware for RequireScope - Add cookbook tree runner scripts for automated testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/orchard9/rdev/internal/auth"
|
|
"github.com/orchard9/rdev/internal/service"
|
|
"github.com/orchard9/rdev/pkg/api"
|
|
)
|
|
|
|
// SDLCOrchestratorHandler handles SDLC orchestration endpoints.
|
|
type SDLCOrchestratorHandler struct {
|
|
orchestrator *service.SDLCOrchestratorService
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewSDLCOrchestratorHandler creates a new orchestrator handler.
|
|
func NewSDLCOrchestratorHandler(orchestrator *service.SDLCOrchestratorService, logger *slog.Logger) *SDLCOrchestratorHandler {
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
return &SDLCOrchestratorHandler{
|
|
orchestrator: orchestrator,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Mount registers orchestration routes under /projects/{id}/sdlc/.
|
|
func (h *SDLCOrchestratorHandler) Mount(r api.Router) {
|
|
r.Route("/projects/{id}/sdlc", func(r chi.Router) {
|
|
// All orchestration operations are write operations
|
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/execute", h.Execute)
|
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/resolve", h.Resolve)
|
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/commit", h.Commit)
|
|
})
|
|
}
|
|
|
|
// Execute runs the next classifier-recommended action.
|
|
// POST /projects/{id}/sdlc/execute
|
|
func (h *SDLCOrchestratorHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
|
|
var req service.ExecuteRequest
|
|
if err := api.DecodeJSON(r, &req); err != nil {
|
|
api.WriteBadRequest(w, r, "invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Feature == "" {
|
|
api.WriteBadRequest(w, r, "feature is required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutLongRunning)
|
|
defer cancel()
|
|
|
|
result, err := h.orchestrator.ExecuteAction(ctx, projectID, &req)
|
|
if err != nil {
|
|
writeSDLCError(w, r, err)
|
|
return
|
|
}
|
|
|
|
api.WriteSuccess(w, r, result)
|
|
}
|
|
|
|
// Resolve unblocks a feature and re-classifies.
|
|
// POST /projects/{id}/sdlc/resolve
|
|
func (h *SDLCOrchestratorHandler) Resolve(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
|
|
var req service.ResolveRequest
|
|
if err := api.DecodeJSON(r, &req); err != nil {
|
|
api.WriteBadRequest(w, r, "invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Feature == "" {
|
|
api.WriteBadRequest(w, r, "feature is required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
|
|
defer cancel()
|
|
|
|
result, err := h.orchestrator.ResolveBlocker(ctx, projectID, &req)
|
|
if err != nil {
|
|
writeSDLCError(w, r, err)
|
|
return
|
|
}
|
|
|
|
api.WriteSuccess(w, r, result)
|
|
}
|
|
|
|
// Commit commits changes in the project pod.
|
|
// POST /projects/{id}/sdlc/commit
|
|
func (h *SDLCOrchestratorHandler) Commit(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
|
|
var req service.CommitRequest
|
|
if err := api.DecodeJSON(r, &req); err != nil {
|
|
api.WriteBadRequest(w, r, "invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Message == "" {
|
|
api.WriteBadRequest(w, r, "message is required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
|
|
defer cancel()
|
|
|
|
result, err := h.orchestrator.CommitChanges(ctx, projectID, &req)
|
|
if err != nil {
|
|
writeSDLCError(w, r, err)
|
|
return
|
|
}
|
|
|
|
api.WriteSuccess(w, r, result)
|
|
}
|