package handlers import ( "net/http" "strings" "time" "github.com/glueops/autoglue/internal/api/httpmiddleware" "github.com/glueops/autoglue/internal/common" "github.com/glueops/autoglue/internal/handlers/dto" "github.com/glueops/autoglue/internal/models" "github.com/glueops/autoglue/internal/utils" "gorm.io/gorm" ) // ListClusters godoc // // @ID ListClusters // @Summary List clusters (org scoped) // @Description Returns clusters for the organization in X-Org-ID. Filter by `q` (name contains). // @Tags Clusters // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param q query string false "Name contains (case-insensitive)" // @Success 200 {array} dto.ClusterResponse // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" // @Failure 500 {string} string "failed to list clusters" // @Router /clusters [get] // @Security BearerAuth // @Security OrgKeyAuth // @Security OrgSecretAuth func ListClusters(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) if !ok { utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") return } q := db.Where("organization_id = ?", orgID) if needle := strings.TrimSpace(r.URL.Query().Get("q")); needle != "" { q = q.Where(`name ILIKE ?`, "%"+needle+"%") } var rows []models.Cluster if err := q. Preload("NodePools"). Preload("NodePools.Labels"). Preload("NodePools.Annotations"). Preload("NodePools.Labels"). Preload("NodePools.Taints"). Preload("NodePools.Servers"). Preload("BastionServer"). Find(&rows).Error; err != nil { utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") return } out := make([]dto.ClusterResponse, 0, len(rows)) for _, row := range rows { out = append(out, clusterToDTO(row)) } } } // CreateCluster godoc // // @ID CreateCluster // @Summary Create cluster (org scoped) // @Description Creates a cluster. If `kubeconfig` is provided, it will be encrypted per-organization and stored securely (never returned). // @Tags Clusters // @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param body body dto.CreateClusterRequest true "payload" // @Success 201 {object} dto.ClusterResponse // @Failure 400 {string} string "invalid json" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" // @Failure 500 {string} string "create failed" // @Router /clusters [post] // @Security BearerAuth // @Security OrgKeyAuth // @Security OrgSecretAuth func CreateCluster(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } } // -- Helpers func clusterToDTO(c models.Cluster) dto.ClusterResponse { var bastion *dto.ServerResponse if c.BastionServer != nil { b := serverToDTO(*c.BastionServer) bastion = &b } nps := make([]dto.NodePoolResponse, 0, len(c.NodePools)) for _, np := range c.NodePools { nps = append(nps, nodePoolToDTO(np)) } return dto.ClusterResponse{ ID: c.ID, Name: c.Name, Provider: c.Provider, Region: c.Region, Status: c.Status, CaptainDomain: c.CaptainDomain, ClusterLoadBalancer: c.ClusterLoadBalancer, RandomToken: c.RandomToken, CertificateKey: c.CertificateKey, ControlLoadBalancer: c.ControlLoadBalancer, NodePools: nps, BastionServer: bastion, CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, } } func nodePoolToDTO(np models.NodePool) dto.NodePoolResponse { labels := make([]dto.LabelResponse, 0, len(np.Labels)) for _, l := range np.Labels { labels = append(labels, dto.LabelResponse{ Key: l.Key, Value: l.Value, }) } annotations := make([]dto.AnnotationResponse, 0, len(np.Annotations)) for _, a := range np.Annotations { annotations = append(annotations, dto.AnnotationResponse{ Key: a.Key, Value: a.Value, }) } taints := make([]dto.TaintResponse, 0, len(np.Taints)) for _, t := range np.Taints { taints = append(taints, dto.TaintResponse{ Key: t.Key, Value: t.Value, Effect: t.Effect, }) } servers := make([]dto.ServerResponse, 0, len(np.Servers)) for _, s := range np.Servers { servers = append(servers, serverToDTO(s)) } return dto.NodePoolResponse{ AuditFields: common.AuditFields{ ID: np.ID, OrganizationID: np.OrganizationID, CreatedAt: np.CreatedAt, UpdatedAt: np.UpdatedAt, }, Name: np.Name, Role: dto.NodeRole(np.Role), Labels: labels, Annotations: annotations, Taints: taints, Servers: servers, } } func serverToDTO(s models.Server) dto.ServerResponse { return dto.ServerResponse{ ID: s.ID, Hostname: s.Hostname, PrivateIPAddress: s.PrivateIPAddress, PublicIPAddress: s.PublicIPAddress, Role: s.Role, Status: s.Status, SSHUser: s.SSHUser, SshKeyID: s.SshKeyID, CreatedAt: s.CreatedAt.UTC().Format(time.RFC3339), UpdatedAt: s.UpdatedAt.UTC().Format(time.RFC3339), } }