package handlers import ( "context" "fmt" "net/http" "strconv" "time" "github.com/go-chi/chi/v5" "github.com/orchard9/rdev/internal/domain" "github.com/orchard9/rdev/pkg/api" ) // PipelineErrorResponse is the JSON representation of a pipeline error. type PipelineErrorResponse struct { Type string `json:"type"` Message string `json:"message"` IsWarning bool `json:"is_warning"` } // PipelineResponse is the JSON representation of a CI pipeline. type PipelineResponse struct { ID int64 `json:"id"` Number int64 `json:"number"` Status string `json:"status"` Event string `json:"event"` Branch string `json:"branch"` Commit string `json:"commit"` Message string `json:"message"` Author string `json:"author"` Started string `json:"started,omitempty"` Finished string `json:"finished,omitempty"` Errors []PipelineErrorResponse `json:"errors,omitempty"` } // ListPipelines returns recent CI pipeline executions for a project. // GET /projects/{id}/pipelines func (h *InfrastructureHandler) ListPipelines(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() if err := validateProjectID(projectID); err != nil { api.WriteBadRequest(w, r, err.Error()) return } if h.ciProvider == nil { api.WriteInternalError(w, r, "CI provider not configured") return } pipelines, err := h.ciProvider.ListPipelines(ctx, h.defaultGitOwner, projectID) if err != nil { api.WriteNotFound(w, r, fmt.Sprintf("pipelines not found: %v", err)) return } resp := make([]PipelineResponse, len(pipelines)) for i, p := range pipelines { resp[i] = PipelineResponse{ ID: p.ID, Number: p.Number, Status: p.Status, Event: p.Event, Branch: p.Branch, Commit: p.Commit, Message: p.Message, Author: p.Author, Started: formatTime(p.Started), Finished: formatTime(p.Finished), Errors: mapPipelineErrors(p.Errors), } } api.WriteSuccess(w, r, resp) } // GetPipeline returns a specific CI pipeline execution for a project. // GET /projects/{id}/pipelines/{number} func (h *InfrastructureHandler) GetPipeline(w http.ResponseWriter, r *http.Request) { projectID := chi.URLParam(r, "id") numberStr := chi.URLParam(r, "number") ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() if err := validateProjectID(projectID); err != nil { api.WriteBadRequest(w, r, err.Error()) return } number, err := strconv.ParseInt(numberStr, 10, 64) if err != nil { api.WriteBadRequest(w, r, "invalid pipeline number") return } if h.ciProvider == nil { api.WriteInternalError(w, r, "CI provider not configured") return } p, err := h.ciProvider.GetPipeline(ctx, h.defaultGitOwner, projectID, number) if err != nil { api.WriteNotFound(w, r, fmt.Sprintf("pipeline not found: %v", err)) return } api.WriteSuccess(w, r, PipelineResponse{ ID: p.ID, Number: p.Number, Status: p.Status, Event: p.Event, Branch: p.Branch, Commit: p.Commit, Message: p.Message, Author: p.Author, Started: formatTime(p.Started), Finished: formatTime(p.Finished), Errors: mapPipelineErrors(p.Errors), }) } // formatTime formats a time.Time as RFC3339, returning empty string for zero time. func formatTime(t time.Time) string { if t.IsZero() { return "" } return t.Format(time.RFC3339) } // mapPipelineErrors converts domain pipeline errors to response format. func mapPipelineErrors(errors []domain.CIPipelineError) []PipelineErrorResponse { if len(errors) == 0 { return nil } resp := make([]PipelineErrorResponse, len(errors)) for i, e := range errors { resp[i] = PipelineErrorResponse{ Type: e.Type, Message: e.Message, IsWarning: e.IsWarning, } } return resp }