Files
ci/internal/handler/build.go
2026-06-18 21:19:47 +08:00

188 lines
4.3 KiB
Go

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"})
}