139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
package api
|
|
|
|
import (
|
|
"clickploy/internal/builder"
|
|
"clickploy/internal/deployer"
|
|
"clickploy/internal/ports"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type Handler struct {
|
|
builder *builder.Builder
|
|
deployer *deployer.Deployer
|
|
ports *ports.Manager
|
|
}
|
|
|
|
func NewHandler(b *builder.Builder, d *deployer.Deployer, p *ports.Manager) *Handler {
|
|
return &Handler{
|
|
builder: b,
|
|
deployer: d,
|
|
ports: p,
|
|
}
|
|
}
|
|
|
|
type DeployRequest struct {
|
|
Repo string `json:"repo" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Port int `json:"port"`
|
|
GitToken string `json:"git_token"`
|
|
}
|
|
type DeployResponse struct {
|
|
Status string `json:"status"`
|
|
AppName string `json:"app_name"`
|
|
Port int `json:"port"`
|
|
URL string `json:"url"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (h *Handler) RegisterRoutes(r *gin.Engine) {
|
|
r.POST("/deploy", h.handleDeploy)
|
|
h.RegisterStreamRoutes(r)
|
|
}
|
|
func (h *Handler) handleDeploy(c *gin.Context) {
|
|
var req DeployRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
imageName, _, err := h.builder.Build(req.Repo, "", req.Name, req.GitToken, "", "", "", "", nil, os.Stdout)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Build failed: %v", err)})
|
|
return
|
|
}
|
|
port, err := h.ports.GetPort(req.Name, req.Port)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Port allocation failed: %v", err)})
|
|
return
|
|
}
|
|
_, err = h.deployer.RunContainer(c.Request.Context(), imageName, req.Name, port, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Deployment failed: %v", err)})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, DeployResponse{
|
|
Status: "success",
|
|
AppName: req.Name,
|
|
Port: port,
|
|
URL: fmt.Sprintf("http://localhost:%d", port),
|
|
Message: "Container started successfully",
|
|
})
|
|
}
|
|
func (h *Handler) RegisterSystemRoutes(r *gin.Engine) {
|
|
r.GET("/api/system/status", h.handleSystemStatus)
|
|
}
|
|
|
|
func (h *Handler) RegisterFrontendRoutes(r *gin.Engine) {
|
|
// Serve static files from the build directory
|
|
r.Static("/_app", "./dist/_app")
|
|
r.StaticFile("/robots.txt", "./dist/robots.txt")
|
|
|
|
// Serve index.html for root
|
|
r.GET("/", func(c *gin.Context) {
|
|
c.File("./dist/index.html")
|
|
})
|
|
|
|
// Handle SPA client-side routing: if no other route matches, serve index.html
|
|
// But ensure we don't serve HTML for API 404s
|
|
r.NoRoute(func(c *gin.Context) {
|
|
path := c.Request.URL.Path
|
|
if len(path) >= 4 && path[:4] == "/api" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "API route not found"})
|
|
return
|
|
}
|
|
c.File("./dist/index.html")
|
|
})
|
|
}
|
|
|
|
func (h *Handler) handleSystemStatus(c *gin.Context) {
|
|
localIP := GetLocalIP()
|
|
publicIP := GetPublicIP()
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"version": "v0.1.0",
|
|
"status": "All systems normal",
|
|
"local_ip": localIP,
|
|
"public_ip": publicIP,
|
|
})
|
|
}
|
|
func GetLocalIP() string {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return "Unknown"
|
|
}
|
|
for _, address := range addrs {
|
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
if ipnet.IP.To4() != nil {
|
|
return ipnet.IP.String()
|
|
}
|
|
}
|
|
}
|
|
return "Unknown"
|
|
}
|
|
func GetPublicIP() string {
|
|
resp, err := http.Get("https://api.ipify.org?format=text")
|
|
if err != nil {
|
|
return "Unknown"
|
|
}
|
|
defer resp.Body.Close()
|
|
ip, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "Unknown"
|
|
}
|
|
return string(ip)
|
|
}
|