188 lines
4.3 KiB
Go
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"})
|
|
}
|