package handler import ( "fmt" "io" "net/http" "time" "context" "ci/internal/model" "ci/internal/service" "github.com/gin-gonic/gin" ) // BuildHandler handles build/run/log endpoints. type BuildHandler struct { Svc *service.ProjectService } func NewBuildHandler(svc *service.ProjectService) *BuildHandler { return &BuildHandler{Svc: svc} } // TriggerBuild starts a build for the project. func (h *BuildHandler) TriggerBuild(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } // Build in background ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) defer cancel() go func() { h.Svc.BuildProject(ctx, id) }() c.JSON(http.StatusAccepted, gin.H{"message": "build started"}) } // StartProject starts the project's binary. func (h *BuildHandler) StartProject(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := h.Svc.StartProject(ctx, id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "started"}) } // StopProject stops a running project. func (h *BuildHandler) StopProject(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } if err := h.Svc.StopProject(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "stopped"}) } // RestartProject restarts a project. func (h *BuildHandler) RestartProject(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := h.Svc.RestartProject(ctx, id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "restarted"}) } // GetBuilds lists build history for a project. func (h *BuildHandler) GetBuilds(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } records, err := h.Svc.GetBuildHistory(id, 50) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if records == nil { records = []model.BuildRecord{} } c.JSON(http.StatusOK, records) } // GetBuildLog returns the full log for a specific build. func (h *BuildHandler) GetBuildLog(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid build id"}) return } logContent, err := h.Svc.GetBuildLog(id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.String(http.StatusOK, logContent) } // StreamLogs streams real-time logs via SSE for a project. func (h *BuildHandler) StreamLogs(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") c.Header("Access-Control-Allow-Origin", "*") ch := h.Svc.SubscribeLogs(id) defer h.Svc.UnsubscribeLogs(id, ch) clientGone := c.Request.Context().Done() c.Stream(func(w io.Writer) bool { select { case <-clientGone: return false case line, ok := <-ch: if !ok { return false } fmt.Fprintf(w, "data: %s\n\n", line) return true } }) } // Webhook handles Git webhook triggers (Gitee/GitHub). func (h *BuildHandler) Webhook(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } // Verify project exists _, err = h.Svc.GetProject(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } // Trigger build ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) defer cancel() go func() { h.Svc.BuildProject(ctx, id) }() c.JSON(http.StatusOK, gin.H{"message": "webhook received, build triggered"}) }