feat(backend): unified response helpers, refresh token, security hardening
- new resp package (OK/Page/Fail/BadRequest/.../InternalErr): unified error
envelope {detail, code?}, sanitized 500s (full error logged server-side,
generic message to client - stops DB error leakage), pagination envelope
{items,total,page,page_size}; migrate 200+ inline c.JSON error sites
- refresh token: 7d rotation tokens (SHA-256 hashed at rest), public
POST /auth/refresh with single-use rotation, POST /auth/logout revokes
all user refresh tokens (endpoint previously missing - frontend 404)
- security: gh-ost command line password masked in task status payload;
Instance.MarshalJSON guarantees encrypted passwords never leave the API
(removes reliance on per-handler manual masking); production (DEBUG=false)
with default JWT secret now auto-generates and persists a random secret
- safe.Go recover wrapper for background goroutines (instance probe,
gh-ost async)
- delete dead service layer (zero callers)
- fix pre-existing go vet unkeyed bson.E literals
This commit is contained in:
1 parent
d5b8b8116c
commit
16f1f7d65f
26 files changed
+916
-963
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"sql-platform/internal/handler/resp"
|
||||
"sql-platform/internal/model"
|
||||
|
||||
"bytes"
|
||||
@@ -28,7 +29,7 @@ func DetailListDatabases(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"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,14 +40,14 @@ func DetailListDatabases(c *gin.Context) {
|
||||
|
||||
db, err := getDBConnection(&instance, "")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SHOW DATABASES")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -72,7 +73,7 @@ func DetailListTables(c *gin.Context) {
|
||||
dbName := c.Query("database")
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,20 +88,20 @@ func DetailListTables(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "database parameter required"})
|
||||
resp.BadRequest(c, "database parameter required")
|
||||
return
|
||||
}
|
||||
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SHOW TABLE STATUS")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -139,7 +140,7 @@ func DetailDbSchema(c *gin.Context) {
|
||||
dbName := c.Query("database")
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -154,13 +155,13 @@ func DetailDbSchema(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "database parameter required"})
|
||||
resp.BadRequest(c, "database parameter required")
|
||||
return
|
||||
}
|
||||
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
@@ -168,7 +169,7 @@ func DetailDbSchema(c *gin.Context) {
|
||||
// Get all tables and their columns
|
||||
tableRows, err := db.Query("SHOW TABLES")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer tableRows.Close()
|
||||
@@ -207,7 +208,7 @@ func DetailTableDetail(c *gin.Context) {
|
||||
tableName := c.Query("table")
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -217,13 +218,13 @@ func DetailTableDetail(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if dbName == "" || tableName == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "database and table parameters required"})
|
||||
resp.BadRequest(c, "database and table parameters required")
|
||||
return
|
||||
}
|
||||
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
@@ -235,7 +236,7 @@ func DetailTableDetail(c *gin.Context) {
|
||||
// Get columns
|
||||
colRows, err := db.Query("SHOW FULL COLUMNS FROM `" + tableName + "`")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer colRows.Close()
|
||||
@@ -306,7 +307,7 @@ func DetailProcesslist(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"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -321,14 +322,14 @@ func DetailProcesslist(c *gin.Context) {
|
||||
}
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SHOW PROCESSLIST")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -365,7 +366,7 @@ func DetailStatus(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"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -383,14 +384,14 @@ func DetailStatus(c *gin.Context) {
|
||||
}
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SHOW GLOBAL STATUS")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -429,7 +430,7 @@ func DetailKillProcess(c *gin.Context) {
|
||||
pid := c.Param("pid")
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -439,14 +440,14 @@ func DetailKillProcess(c *gin.Context) {
|
||||
}
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec("KILL " + pid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Kill failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Process " + pid + " killed"})
|
||||
@@ -457,7 +458,7 @@ func DetailListUsers(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"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -472,7 +473,7 @@ func DetailListUsers(c *gin.Context) {
|
||||
}
|
||||
db, err := getDBConnection(&instance, dbName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": fmt.Sprintf("Connection failed: %v", err)})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
@@ -514,11 +515,11 @@ func ExecuteQuery(c *gin.Context) {
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if input.InstanceID == 0 || input.SQL == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "instance_id and sql are required"})
|
||||
resp.BadRequest(c, "instance_id and sql are required")
|
||||
return
|
||||
}
|
||||
// Read query limits from platform settings
|
||||
@@ -538,7 +539,7 @@ func ExecuteQuery(c *gin.Context) {
|
||||
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, input.InstanceID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -607,7 +608,7 @@ func ExecuteQuery(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "Unsupported database type: " + instance.DBType})
|
||||
resp.BadRequest(c, "Unsupported database type: "+instance.DBType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,13 +1248,13 @@ func executeMongoQuery(instance *model.Instance, dbName string, queryStr string,
|
||||
if pipeline != nil {
|
||||
// Aggregation — add $facet for count + data
|
||||
facetStage := bson.D{
|
||||
{"$facet", bson.D{
|
||||
{"data", bson.A{
|
||||
bson.D{{"$skip", offset}},
|
||||
bson.D{{"$limit", limit}},
|
||||
{Key: "$facet", Value: bson.D{
|
||||
{Key: "data", Value: bson.A{
|
||||
bson.D{{Key: "$skip", Value: offset}},
|
||||
bson.D{{Key: "$limit", Value: limit}},
|
||||
}},
|
||||
{"count", bson.A{
|
||||
bson.D{{"$count", "total"}},
|
||||
{Key: "count", Value: bson.A{
|
||||
bson.D{{Key: "$count", Value: "total"}},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
@@ -1404,11 +1405,11 @@ func ExportQueryCSV(c *gin.Context) {
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if input.InstanceID == 0 || input.SQL == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "instance_id and sql are required"})
|
||||
resp.BadRequest(c, "instance_id and sql are required")
|
||||
return
|
||||
}
|
||||
if input.Limit <= 0 {
|
||||
@@ -1420,7 +1421,7 @@ func ExportQueryCSV(c *gin.Context) {
|
||||
|
||||
var instance model.Instance
|
||||
if err := model.DB.First(&instance, input.InstanceID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance not found"})
|
||||
resp.NotFound(c, "Instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1438,7 +1439,7 @@ func ExportQueryCSV(c *gin.Context) {
|
||||
case "mongodb", "mongo":
|
||||
exportMongoCSV(c, &instance, input.Database, input.SQL, input.Limit)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "CSV export not supported for " + instance.DBType})
|
||||
resp.BadRequest(c, "CSV export not supported for "+instance.DBType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user