187
internal/handler/build.go
Normal file
187
internal/handler/build.go
Normal file
@@ -0,0 +1,187 @@
|
||||
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"})
|
||||
}
|
||||
Reference in New Issue
Block a user