rdev/internal/handlers/sdlc_generate.go
jordan 00f55f7f6f
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix(sdlc): route conflict with SDLCGenerateHandler shadowing SDLC routes
SDLCGenerateHandler was using r.Route() to create a sub-router at
/projects/{id}/sdlc/features/{slug}, which shadowed SDLCHandler's
nested routes like /features/{slug}/artifacts/{type}/approve.

Changed to direct route registration to avoid chi route conflicts.
This fixes 404 errors on SDLC feature and artifact endpoints.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 11:27:41 -07:00

90 lines
2.8 KiB
Go

package handlers
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/orchard9/rdev/internal/auth"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/service"
"github.com/orchard9/rdev/pkg/api"
)
// SDLCGenerateHandler handles async SDLC artifact generation endpoints.
type SDLCGenerateHandler struct {
generateSvc *service.SDLCGenerateService
}
// NewSDLCGenerateHandler creates a new SDLC generate handler.
func NewSDLCGenerateHandler(generateSvc *service.SDLCGenerateService) *SDLCGenerateHandler {
return &SDLCGenerateHandler{
generateSvc: generateSvc,
}
}
// Mount registers the generate endpoint on the router.
// Note: Uses direct route to avoid conflict with /projects/{id}/sdlc in sdlc.go.
// Creating a Route() group at /projects/{id}/sdlc/features/{slug} shadows
// the SDLCHandler's nested routes like /features/{slug}/artifacts/{type}/approve.
func (h *SDLCGenerateHandler) Mount(r api.Router) {
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
Post("/projects/{id}/sdlc/features/{slug}/generate", h.Generate)
}
// GenerateRequest is the request body for POST /projects/{id}/sdlc/features/{slug}/generate.
type GenerateRequest struct {
// ArtifactType is the type of artifact to generate: spec, design, tasks, code, qa
ArtifactType string `json:"artifact_type"`
// TaskID is the specific task to implement (required for artifact_type: "code")
TaskID string `json:"task_id,omitempty"`
// Provider specifies which code agent to use (optional)
Provider string `json:"provider,omitempty"`
}
// Generate handles POST /projects/{id}/sdlc/features/{slug}/generate.
// It enqueues an async task to generate an SDLC artifact.
func (h *SDLCGenerateHandler) Generate(w http.ResponseWriter, r *http.Request) {
projectID := chi.URLParam(r, "id")
featureSlug := chi.URLParam(r, "slug")
var req GenerateRequest
if err := api.DecodeJSON(r, &req); err != nil {
api.WriteBadRequest(w, r, "invalid request body")
return
}
// Validate artifact_type
if req.ArtifactType == "" {
api.WriteBadRequest(w, r, "artifact_type is required")
return
}
if !domain.IsValidGenerateArtifactType(req.ArtifactType) {
api.WriteBadRequest(w, r, "invalid artifact_type: must be one of spec, design, tasks, code, qa")
return
}
// Validate task_id is provided for code generation
if req.ArtifactType == "code" && req.TaskID == "" {
api.WriteBadRequest(w, r, "task_id is required for artifact_type: code")
return
}
ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
defer cancel()
result, err := h.generateSvc.GenerateArtifact(ctx, projectID, featureSlug, &domain.SDLCGenerateRequest{
ArtifactType: req.ArtifactType,
TaskID: req.TaskID,
Provider: req.Provider,
})
if err != nil {
writeSDLCError(w, r, err)
return
}
api.WriteJSON(w, r, http.StatusAccepted, result)
}