feat: v0.3.1 — Go/Gin 后端重写 + gh-ost 集成
架构变更: - Python/FastAPI → Go/Gin 重写(Python 后端保留在 backend-python/) - GORM + SQLite(兼容现有数据库表结构) - JWT 认证 + RBAC 权限中间件 Go 后端结构: - cmd/server/main.go — 入口,端口 9000 - config/config.go — 环境变量配置 - internal/model/ — GORM 模型(30张表,完全兼容 SQLite) - internal/handler/ — auth/instance/ticket/dashboard/ghost handler - internal/middleware/ — JWT 认证 + CORS - internal/ghost/ — gh-ost 集成模块(同步/异步执行,任务管理) - router/router.go — API 路由注册(/api/v1/...) gh-ost 集成: - RunGhOst() 同步执行 - RunGhOstAsync() 异步执行 + 进度追踪 - TaskManager 管理多个并发任务 - API: POST /ghost/dry-run, /ghost/execute, GET /ghost/status/:taskId 核心 API 已对齐: - POST /auth/login (JWT) - GET /dashboard/stats, /dashboard/my-tickets - GET /tickets/statistics, CRUD + approve/execute - GET/POST/PUT/DELETE /instances - GET /users, /audit-rules, /announcements, /settings 已验证: - 登录 admin/admin123 ✅ - Dashboard 99工单/110查询/4实例 ✅ - 工单统计按角色过滤 ✅
This commit is contained in:
1 parent
43d1f3a234
commit
ca308e277d
202 files changed
+2265
-51181
No files matched your search
@@ -0,0 +1,153 @@
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: actual DB connection test
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Connection test passed (placeholder)",
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user