package handlers import ( "context" "net/http" "github.com/go-chi/chi/v5" "github.com/orchard9/rdev/internal/sdlc" "github.com/orchard9/rdev/internal/validate" "github.com/orchard9/rdev/pkg/api" ) // CreateFeatureRequest is the request body for POST /projects/{id}/sdlc/features. type CreateFeatureRequest struct { Slug string `json:"slug"` Title string `json:"title"` } // TransitionFeatureRequest is the request body for POST /projects/{id}/sdlc/features/{slug}/transition. type TransitionFeatureRequest struct { Phase string `json:"phase"` } // BlockFeatureRequest is the request body for POST /projects/{id}/sdlc/features/{slug}/block. type BlockFeatureRequest struct { Reason string `json:"reason"` } // ListFeatures returns all features in a project. // GET /projects/{id}/sdlc/features func (h *SDLCHandler) ListFeatures(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard) defer cancel() features, err := h.sdlcService.ListFeatures(ctx, projectID) if err != nil { writeSDLCError(w, r, err) return } if features == nil { features = []*sdlc.Feature{} } api.WriteSuccess(w, r, features) } // GetFeature returns a single feature by slug. // GET /projects/{id}/sdlc/features/{slug} func (h *SDLCHandler) GetFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") slug := chi.URLParam(r, "slug") ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard) defer cancel() feature, err := h.sdlcService.GetFeature(ctx, projectID, slug) if err != nil { writeSDLCError(w, r, err) return } api.WriteSuccess(w, r, feature) } // CreateFeature creates a new feature. // POST /projects/{id}/sdlc/features func (h *SDLCHandler) CreateFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") var req CreateFeatureRequest if err := api.DecodeJSON(r, &req); err != nil { api.WriteBadRequest(w, r, "invalid request body") return } v := validate.New() v.Required(req.Slug, "slug") v.Required(req.Title, "title") if err := v.Error(); err != nil { api.WriteBadRequest(w, r, err.Error()) return } ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) defer cancel() feature, err := h.sdlcService.CreateFeature(ctx, projectID, req.Slug, req.Title) if err != nil { writeSDLCError(w, r, err) return } api.WriteCreated(w, r, feature) } // TransitionFeature moves a feature to a new phase. // POST /projects/{id}/sdlc/features/{slug}/transition func (h *SDLCHandler) TransitionFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") slug := chi.URLParam(r, "slug") var req TransitionFeatureRequest if err := api.DecodeJSON(r, &req); err != nil { api.WriteBadRequest(w, r, "invalid request body") return } if req.Phase == "" { api.WriteBadRequest(w, r, "phase is required") return } phase := sdlc.FeaturePhase(req.Phase) if !sdlc.IsValidPhase(phase) { api.WriteBadRequest(w, r, "invalid phase: "+req.Phase) return } ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) defer cancel() if err := h.sdlcService.TransitionFeature(ctx, projectID, slug, phase); err != nil { writeSDLCError(w, r, err) return } api.WriteSuccess(w, r, map[string]any{ "feature": slug, "phase": req.Phase, "message": "feature transitioned successfully", }) } // BlockFeature blocks a feature with a reason. // POST /projects/{id}/sdlc/features/{slug}/block func (h *SDLCHandler) BlockFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") slug := chi.URLParam(r, "slug") var req BlockFeatureRequest if err := api.DecodeJSON(r, &req); err != nil { api.WriteBadRequest(w, r, "invalid request body") return } if req.Reason == "" { api.WriteBadRequest(w, r, "reason is required") return } ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) defer cancel() if err := h.sdlcService.BlockFeature(ctx, projectID, slug, req.Reason); err != nil { writeSDLCError(w, r, err) return } api.WriteSuccess(w, r, map[string]any{ "feature": slug, "message": "feature blocked", }) } // UnblockFeature removes all blockers from a feature. // POST /projects/{id}/sdlc/features/{slug}/unblock func (h *SDLCHandler) UnblockFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") slug := chi.URLParam(r, "slug") ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) defer cancel() if err := h.sdlcService.UnblockFeature(ctx, projectID, slug); err != nil { writeSDLCError(w, r, err) return } api.WriteSuccess(w, r, map[string]any{ "feature": slug, "message": "feature unblocked", }) } // DeleteFeature removes a feature. // DELETE /projects/{id}/sdlc/features/{slug} func (h *SDLCHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") slug := chi.URLParam(r, "slug") ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) defer cancel() if err := h.sdlcService.DeleteFeature(ctx, projectID, slug); err != nil { writeSDLCError(w, r, err) return } api.WriteNoContent(w) }