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:
Logan.Li committed 2026-10-26 17:21:00 +08:00
1 parent d5b8b8116c
commit 16f1f7d65f
26 files changed
+916 -963

No files matched your search

+11 -10
View File
@@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"sql-platform/internal/handler/resp"
"sql-platform/internal/model"
"github.com/gin-gonic/gin"
@@ -17,7 +18,7 @@ func GetInstanceGroup(c *gin.Context) {
id := c.Param("id")
var group model.InstanceGroup
if err := model.DB.First(&group, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance group not found"})
resp.NotFound(c, "Instance group not found")
return
}
c.JSON(http.StatusOK, enrichGroup(group))
@@ -34,7 +35,7 @@ func CreateInstanceGroup(c *gin.Context) {
WriteInstanceIDs []int `json:"write_instance_ids"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
resp.BadRequest(c, err.Error())
return
}
@@ -58,7 +59,7 @@ func CreateInstanceGroup(c *gin.Context) {
IsActive: &isActive,
}
if err := model.DB.Create(&group).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
resp.InternalErr(c, err)
return
}
@@ -83,7 +84,7 @@ func UpdateInstanceGroup(c *gin.Context) {
gid, _ := strconv.Atoi(id)
var group model.InstanceGroup
if err := model.DB.First(&group, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "Instance group not found"})
resp.NotFound(c, "Instance group not found")
return
}
@@ -100,7 +101,7 @@ func UpdateInstanceGroup(c *gin.Context) {
WriteInstanceIDs *[]int `json:"write_instance_ids"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
resp.BadRequest(c, err.Error())
return
}
@@ -132,7 +133,7 @@ func UpdateInstanceGroup(c *gin.Context) {
if len(updates) > 0 {
if err := model.DB.Model(&group).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
resp.InternalErr(c, err)
return
}
}
@@ -175,7 +176,7 @@ func UpdateInstanceGroup(c *gin.Context) {
func DeleteInstanceGroup(c *gin.Context) {
id := c.Param("id")
if err := model.DB.Delete(&model.InstanceGroup{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
resp.InternalErr(c, err)
return
}
// also clean up members
@@ -191,7 +192,7 @@ func AssignGroupInstances(c *gin.Context) {
AccessType string `json:"access_type"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
resp.BadRequest(c, err.Error())
return
}
if input.AccessType == "" {
@@ -240,7 +241,7 @@ func AssignGroupUsers(c *gin.Context) {
Permissions []string `json:"permissions"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
resp.BadRequest(c, err.Error())
return
}
@@ -278,7 +279,7 @@ func UpdateUserGroups(c *gin.Context) {
GroupIDs []int `json:"group_ids"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
resp.BadRequest(c, err.Error())
return
}