package transport import ( "net/http" "strconv" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "app-deploy-platform/backend/internal/config" "app-deploy-platform/backend/internal/model" "app-deploy-platform/backend/internal/service" ) type routerHandler struct { manager *service.Manager } type hostPayload struct { ID uint `json:"id"` Name string `json:"name"` InstanceID string `json:"instance_id"` PrivateIP string `json:"private_ip"` Role string `json:"role"` Environment string `json:"environment"` Description string `json:"description"` TATOnline bool `json:"tat_online"` } type instancePayload struct { ID uint `json:"id"` ServiceName string `json:"service_name"` HostID uint `json:"host_id"` Port int `json:"port"` UnitName string `json:"unit_name"` HealthPath string `json:"health_path"` ReadyPath string `json:"ready_path"` CurrentReleaseID string `json:"current_release_id"` } type releasePayload struct { ID uint `json:"id"` ServiceName string `json:"service_name"` ReleaseID string `json:"release_id"` GitSHA string `json:"git_sha"` COSKey string `json:"cos_key"` SHA256 string `json:"sha256"` ArtifactURL string `json:"artifact_url"` BuildHost string `json:"build_host"` } type deploymentPayload struct { ServiceName string `json:"service_name"` ReleaseID string `json:"release_id"` Operation string `json:"operation"` Operator string `json:"operator"` HostIDs []uint `json:"host_ids"` } type buildPayload struct { ServiceName string `json:"service_name"` ReleaseID string `json:"release_id"` Branch string `json:"branch"` Operator string `json:"operator"` } type releaseRunPayload struct { ReleaseID string `json:"release_id"` Operator string `json:"operator"` } func NewRouter(cfg config.Config, manager *service.Manager) *gin.Engine { gin.SetMode(gin.ReleaseMode) router := gin.New() router.Use(gin.Logger(), gin.Recovery()) corsConfig := cors.Config{ AllowOrigins: []string{cfg.Platform.CORSOrigin}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Content-Type", "Authorization"}, AllowCredentials: cfg.Platform.CORSOrigin != "*", } if cfg.Platform.CORSOrigin == "*" { corsConfig.AllowAllOrigins = true } router.Use(cors.New(corsConfig)) handler := &routerHandler{manager: manager} router.GET("/api/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) api := router.Group("/api") { api.GET("/overview", handler.overview) api.GET("/hosts", handler.listHosts) api.POST("/hosts", handler.saveHost) api.PUT("/hosts/:id", handler.saveHost) api.DELETE("/hosts/:id", handler.deleteHost) api.GET("/instances", handler.listInstances) api.POST("/instances", handler.saveInstance) api.PUT("/instances/:id", handler.saveInstance) api.DELETE("/instances/:id", handler.deleteInstance) api.POST("/instances/:id/restart", handler.restartInstance) api.GET("/services", handler.listServices) api.GET("/releases", handler.listReleases) api.POST("/releases", handler.saveRelease) api.GET("/builds", handler.listBuilds) api.POST("/builds", handler.createBuild) api.GET("/release-runs", handler.listReleaseRuns) api.GET("/release-runs/:id", handler.getReleaseRun) api.POST("/release-runs", handler.createReleaseRun) api.GET("/deployments", handler.listDeployments) api.GET("/deployments/:id", handler.getDeployment) api.POST("/deployments", handler.createDeployment) api.POST("/deployments/:id/rollback", handler.rollbackDeployment) } return router } func (h *routerHandler) overview(c *gin.Context) { data, err := h.manager.GetOverview(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) listHosts(c *gin.Context) { data, err := h.manager.ListHosts(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) saveHost(c *gin.Context) { var payload hostPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } id := payload.ID if id == 0 { id = uintFromParam(c.Param("id")) } host := &model.Host{ Model: model.Host{}.Model, Name: payload.Name, InstanceID: payload.InstanceID, PrivateIP: payload.PrivateIP, Role: payload.Role, Environment: payload.Environment, Description: payload.Description, TATOnline: payload.TATOnline, } host.ID = id if err := h.manager.SaveHost(c.Request.Context(), host); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, host) } func (h *routerHandler) deleteHost(c *gin.Context) { if err := h.manager.DeleteHost(c.Request.Context(), uintFromParam(c.Param("id"))); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Status(http.StatusNoContent) } func (h *routerHandler) listInstances(c *gin.Context) { data, err := h.manager.ListInstances(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) saveInstance(c *gin.Context) { var payload instancePayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } id := payload.ID if id == 0 { id = uintFromParam(c.Param("id")) } instance := &model.ServiceInstance{ ServiceName: payload.ServiceName, HostID: payload.HostID, Port: payload.Port, UnitName: payload.UnitName, HealthPath: payload.HealthPath, ReadyPath: payload.ReadyPath, CurrentReleaseID: payload.CurrentReleaseID, } instance.ID = id if err := h.manager.SaveInstance(c.Request.Context(), instance); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, instance) } func (h *routerHandler) deleteInstance(c *gin.Context) { if err := h.manager.DeleteInstance(c.Request.Context(), uintFromParam(c.Param("id"))); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Status(http.StatusNoContent) } func (h *routerHandler) restartInstance(c *gin.Context) { operator := c.Query("operator") data, err := h.manager.CreateRestartForInstance(c.Request.Context(), uintFromParam(c.Param("id")), operator) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusAccepted, data) } func (h *routerHandler) listServices(c *gin.Context) { c.JSON(http.StatusOK, h.manager.ListServices(c.Request.Context())) } func (h *routerHandler) listReleases(c *gin.Context) { data, err := h.manager.ListReleases(c.Request.Context(), c.Query("service_name")) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) saveRelease(c *gin.Context) { var payload releasePayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } release := &model.Release{ ServiceName: payload.ServiceName, ReleaseID: payload.ReleaseID, GitSHA: payload.GitSHA, COSKey: payload.COSKey, SHA256: payload.SHA256, ArtifactURL: payload.ArtifactURL, BuildHost: payload.BuildHost, } release.ID = payload.ID if err := h.manager.SaveRelease(c.Request.Context(), release); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, release) } func (h *routerHandler) listBuilds(c *gin.Context) { data, err := h.manager.ListBuilds(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) createBuild(c *gin.Context) { var payload buildPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } data, err := h.manager.CreateBuild(c.Request.Context(), service.CreateBuildRequest{ ServiceName: payload.ServiceName, ReleaseID: payload.ReleaseID, Branch: payload.Branch, Operator: payload.Operator, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusAccepted, data) } func (h *routerHandler) listReleaseRuns(c *gin.Context) { data, err := h.manager.ListReleaseRuns(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) getReleaseRun(c *gin.Context) { data, err := h.manager.GetReleaseRun(c.Request.Context(), uintFromParam(c.Param("id"))) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) createReleaseRun(c *gin.Context) { var payload releaseRunPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } data, err := h.manager.CreateReleaseRun(c.Request.Context(), service.CreateReleaseRunRequest{ ReleaseID: payload.ReleaseID, Operator: payload.Operator, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusAccepted, data) } func (h *routerHandler) listDeployments(c *gin.Context) { data, err := h.manager.ListDeployments(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) getDeployment(c *gin.Context) { data, err := h.manager.GetDeployment(c.Request.Context(), uintFromParam(c.Param("id"))) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, data) } func (h *routerHandler) createDeployment(c *gin.Context) { var payload deploymentPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } data, err := h.manager.CreateDeployment(c.Request.Context(), service.CreateDeploymentRequest{ ServiceName: payload.ServiceName, ReleaseID: payload.ReleaseID, Operation: payload.Operation, Operator: payload.Operator, HostIDs: payload.HostIDs, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusAccepted, data) } func (h *routerHandler) rollbackDeployment(c *gin.Context) { var payload struct { ReleaseID string `json:"release_id"` Operator string `json:"operator"` } if err := c.ShouldBindJSON(&payload); err != nil && err.Error() != "EOF" { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } data, err := h.manager.CreateRollback(c.Request.Context(), uintFromParam(c.Param("id")), payload.ReleaseID, payload.Operator) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusAccepted, data) } func uintFromParam(raw string) uint { value, _ := strconv.ParseUint(raw, 10, 64) return uint(value) }