package handlers import ( "errors" "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/internal/validate" "github.com/orchard9/rdev/pkg/api" ) // BlueprintsHandler handles blueprint endpoints. type BlueprintsHandler struct { blueprintService *service.BlueprintService } // NewBlueprintsHandler creates a new blueprints handler. func NewBlueprintsHandler(blueprintService *service.BlueprintService) *BlueprintsHandler { return &BlueprintsHandler{ blueprintService: blueprintService, } } // Mount registers blueprint routes. func (h *BlueprintsHandler) Mount(r api.Router) { r.Route("/projects/{id}/blueprints", func(r chi.Router) { // Read operations r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)). Get("/", h.ListBlueprints) r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)). Get("/{blueprintId}", h.GetBlueprint) // Write operations r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)). Post("/", h.CreateBlueprint) r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)). Put("/{blueprintId}", h.UpdateBlueprint) r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)). Delete("/{blueprintId}", h.DeleteBlueprint) }) } // CreateBlueprintRequest is the request body for POST /projects/{id}/blueprints. type CreateBlueprintRequest struct { Name string `json:"name"` Description string `json:"description,omitempty"` Spec map[string]any `json:"spec"` } // CreateBlueprint creates a new blueprint. // POST /projects/{id}/blueprints func (h *BlueprintsHandler) CreateBlueprint(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") var req CreateBlueprintRequest if err := api.DecodeJSON(r, &req); err != nil { api.WriteBadRequest(w, r, "invalid request body") return } v := validate.New() v.Required(req.Name, "name") if err := v.Error(); err != nil { api.WriteBadRequest(w, r, err.Error()) return } blueprint, err := h.blueprintService.CreateBlueprint(r.Context(), projectID, req.Name, req.Description, req.Spec) if err != nil { api.WriteInternalError(w, r, "failed to create blueprint") return } api.WriteCreated(w, r, toBlueprintDTO(blueprint)) } // ListBlueprints returns all blueprints for a project. // GET /projects/{id}/blueprints func (h *BlueprintsHandler) ListBlueprints(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") blueprints, err := h.blueprintService.ListBlueprints(r.Context(), projectID) if err != nil { api.WriteInternalError(w, r, "failed to list blueprints") return } dtos := make([]*BlueprintDTO, len(blueprints)) for i, b := range blueprints { dtos[i] = toBlueprintDTO(b) } api.WriteSuccess(w, r, map[string]any{ "blueprints": dtos, "total": len(dtos), }) } // GetBlueprint retrieves a blueprint by ID. // GET /projects/{id}/blueprints/{blueprintId} func (h *BlueprintsHandler) GetBlueprint(w http.ResponseWriter, r *http.Request) { blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId")) blueprint, err := h.blueprintService.GetBlueprint(r.Context(), blueprintID) if err != nil { if errors.Is(err, domain.ErrBlueprintNotFound) { api.WriteNotFound(w, r, "blueprint not found") return } api.WriteInternalError(w, r, "failed to get blueprint") return } api.WriteSuccess(w, r, toBlueprintDTO(blueprint)) } // UpdateBlueprintRequest is the request body for PUT /projects/{id}/blueprints/{blueprintId}. type UpdateBlueprintRequest struct { Name string `json:"name"` Description string `json:"description,omitempty"` Spec map[string]any `json:"spec"` } // UpdateBlueprint updates a blueprint. // PUT /projects/{id}/blueprints/{blueprintId} func (h *BlueprintsHandler) UpdateBlueprint(w http.ResponseWriter, r *http.Request) { blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId")) var req UpdateBlueprintRequest if err := api.DecodeJSON(r, &req); err != nil { api.WriteBadRequest(w, r, "invalid request body") return } if err := h.blueprintService.UpdateBlueprint(r.Context(), blueprintID, req.Name, req.Description, req.Spec); err != nil { if errors.Is(err, domain.ErrBlueprintNotFound) { api.WriteNotFound(w, r, "blueprint not found") return } api.WriteInternalError(w, r, "failed to update blueprint") return } api.WriteSuccess(w, r, map[string]any{ "message": "blueprint updated", }) } // DeleteBlueprint deletes a blueprint. // DELETE /projects/{id}/blueprints/{blueprintId} func (h *BlueprintsHandler) DeleteBlueprint(w http.ResponseWriter, r *http.Request) { blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId")) if err := h.blueprintService.DeleteBlueprint(r.Context(), blueprintID); err != nil { if errors.Is(err, domain.ErrBlueprintNotFound) { api.WriteNotFound(w, r, "blueprint not found") return } api.WriteInternalError(w, r, "failed to delete blueprint") return } api.WriteSuccess(w, r, map[string]any{ "message": "blueprint deleted", }) }