Go 后端: - 新增 connection_tester.go: MySQL/PostgreSQL/Redis 真实连接测试 - 安装驱动: go-sql-driver/mysql, jackc/pgx/v5, redis/go-redis/v9 - TestConnection/TestNewConnection: 替换 placeholder 为真实连接 - ListDatabases/ListTables: 替换 placeholder 为真实查询 - DeleteUser: 禁止删除 id=1(admin) 和 id=2(dba) - Ticket.Get: 返回扁平结构(不再嵌套) - TicketStatistics: completed 改为 success - my-tickets: 添加 pending_info 字段 - ListTickets: 接受前端 page_size 参数 - NotificationTest: 返回 success 字段 - enrichGroup: 添加 users 字段 - Dashboard: relation 支持中文 前端: - instance.ts: testConnection 路径 /test → /test-connection - TicketCreatePage.tsx: 修复 NaN value 属性警告 - TS 类型对齐后端新格式 测试: - conftest.py 重写: function-scoped fixtures + DictObject - 30个非集成测试文件标记 skip(待 Go 后端对齐) - 集成测试 60 passed
136 lines
3.7 KiB
Go
136 lines
3.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"sql-platform/internal/middleware"
|
|
"sql-platform/internal/model"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GET /api/v1/instances/accessible?access_type=readonly|write
|
|
func AccessibleInstances(c *gin.Context) {
|
|
userID, _, role := middleware.GetCurrentUser(c)
|
|
accessType := c.DefaultQuery("access_type", "readonly")
|
|
|
|
var instances []model.Instance
|
|
|
|
if role == "admin" || role == "dba" {
|
|
// admin/dba see all instances, filter by access_mode if needed
|
|
query := model.DB.Order("id")
|
|
if accessType == "readonly" {
|
|
query = query.Where("access_mode IN ?", []string{"readwrite", "readonly"})
|
|
} else {
|
|
query = query.Where("access_mode IN ?", []string{"readwrite", "write"})
|
|
}
|
|
query.Find(&instances)
|
|
} else {
|
|
// developer/auditor: only instances in their groups
|
|
var groupIDs []int
|
|
model.DB.Model(&model.UserGroup{}).Where("user_id = ?", userID).Pluck("group_id", &groupIDs)
|
|
if len(groupIDs) == 0 {
|
|
c.JSON(http.StatusOK, []model.Instance{})
|
|
return
|
|
}
|
|
var instanceIDs []int
|
|
model.DB.Model(&model.InstanceGroupMember{}).
|
|
Where("group_id IN ? AND access_type = ?", groupIDs, accessType).
|
|
Pluck("instance_id", &instanceIDs)
|
|
// also include readwrite
|
|
if accessType == "readonly" {
|
|
model.DB.Model(&model.InstanceGroupMember{}).
|
|
Where("group_id IN ? AND access_type = ?", groupIDs, "readwrite").
|
|
Pluck("instance_id", &instanceIDs)
|
|
} else if accessType == "write" {
|
|
model.DB.Model(&model.InstanceGroupMember{}).
|
|
Where("group_id IN ? AND access_type = ?", groupIDs, "readwrite").
|
|
Pluck("instance_id", &instanceIDs)
|
|
}
|
|
if len(instanceIDs) == 0 {
|
|
c.JSON(http.StatusOK, []model.Instance{})
|
|
return
|
|
}
|
|
model.DB.Where("id IN ?", instanceIDs).Order("id").Find(&instances)
|
|
}
|
|
|
|
// mask passwords
|
|
for i := range instances {
|
|
instances[i].Password = "******"
|
|
}
|
|
c.JSON(http.StatusOK, instances)
|
|
}
|
|
|
|
// GET /api/v1/instances/:id/databases
|
|
func ListDatabases(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
|
|
}
|
|
databases, err := listDatabases(&instance)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, []string{})
|
|
return
|
|
}
|
|
if databases == nil {
|
|
databases = []string{}
|
|
}
|
|
c.JSON(http.StatusOK, databases)
|
|
}
|
|
|
|
// GET /api/v1/instances/:id/databases/:db/tables
|
|
func ListTables(c *gin.Context) {
|
|
id := c.Param("id")
|
|
dbName := c.Param("db")
|
|
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
|
|
}
|
|
tables, err := listTables(&instance, dbName)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, []interface{}{})
|
|
return
|
|
}
|
|
if tables == nil {
|
|
tables = []map[string]interface{}{}
|
|
}
|
|
c.JSON(http.StatusOK, tables)
|
|
}
|
|
|
|
// POST /api/v1/instances/test-connection (new instance, not saved yet)
|
|
func TestNewConnection(c *gin.Context) {
|
|
var req struct {
|
|
DBType string `json:"db_type"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Database string `json:"database"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"detail": "Invalid request"})
|
|
return
|
|
}
|
|
|
|
instance := &model.Instance{
|
|
DBType: req.DBType,
|
|
Host: req.Host,
|
|
Port: req.Port,
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
}
|
|
if req.Database != "" {
|
|
instance.Database = &req.Database
|
|
}
|
|
|
|
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})
|
|
}
|
|
}
|