package handler import ( "net/http" "strconv" "sql-platform/internal/middleware" "sql-platform/internal/model" "github.com/gin-gonic/gin" ) // GET /api/v1/instances func ListInstances(c *gin.Context) { userID, _, role := middleware.GetCurrentUser(c) var instances []model.Instance query := model.DB.Order("id") // developer only sees instances in their groups if role == "developer" { var groupIDs []int model.DB.Model(&model.UserGroup{}).Where("user_id = ?", userID).Pluck("group_id", &groupIDs) if len(groupIDs) > 0 { var instanceIDs []int model.DB.Model(&model.InstanceGroupMember{}).Where("group_id IN ?", groupIDs).Pluck("instance_id", &instanceIDs) if len(instanceIDs) > 0 { query = query.Where("id IN ?", instanceIDs) } else { c.JSON(http.StatusOK, []model.Instance{}) return } } } if err := query.Find(&instances).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return } // mask passwords in response for i := range instances { instances[i].Password = "" } c.JSON(http.StatusOK, instances) } // GET /api/v1/instances/:id func GetInstance(c *gin.Context) { id := c.Param("id") var instance model.Instance if err := model.DB.First(&instance, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"}) return } instance.Password = "******" c.JSON(http.StatusOK, instance) } // POST /api/v1/instances func CreateInstance(c *gin.Context) { var instance model.Instance if err := c.ShouldBindJSON(&instance); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } if err := model.DB.Create(&instance).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return } instance.Password = "******" c.JSON(http.StatusCreated, instance) } // PUT /api/v1/instances/:id func UpdateInstance(c *gin.Context) { id := c.Param("id") var instance model.Instance if err := model.DB.First(&instance, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"}) return } var input map[string]interface{} if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } // if password is masked, don't update it if pw, ok := input["password"].(string); ok && pw == "******" { delete(input, "password") } if err := model.DB.Model(&instance).Updates(input).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return } model.DB.First(&instance, id) instance.Password = "******" c.JSON(http.StatusOK, instance) } // DELETE /api/v1/instances/:id func DeleteInstance(c *gin.Context) { id := c.Param("id") if err := model.DB.Delete(&model.Instance{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Instance deleted"}) } // POST /api/v1/instances/:id/test-connection func TestConnection(c *gin.Context) { id := c.Param("id") var instance model.Instance if err := model.DB.First(&instance, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"}) return } success, message := testDBConnection(&instance) if success { c.JSON(http.StatusOK, gin.H{"success": true, "message": message}) } else { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": message}) } } // GET /api/v1/instances/stats func InstanceStats(c *gin.Context) { var total int64 model.DB.Model(&model.Instance{}).Count(&total) type typeCount struct { DBType string `json:"db_type"` Count int64 `json:"count"` } var byType []typeCount model.DB.Model(&model.Instance{}).Select("db_type, count(*) as count").Group("db_type").Scan(&byType) c.JSON(http.StatusOK, gin.H{ "total": total, "by_type": byType, }) } // Helper: parse id param to int func parseID(s string) (int, bool) { id, err := strconv.Atoi(s) return id, err == nil }