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
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"sql-platform/internal/handler/resp"
|
||||
"sql-platform/internal/middleware"
|
||||
"sql-platform/internal/model"
|
||||
|
||||
@@ -28,7 +29,7 @@ func ListScheduledTasks(c *gin.Context) {
|
||||
"applicant_id = ? OR id IN (SELECT ticket_id FROM ticket_approvals WHERE approver_id = ?)",
|
||||
userID, userID,
|
||||
)
|
||||
// admin sees everything
|
||||
// admin sees everything
|
||||
}
|
||||
|
||||
var total int64
|
||||
@@ -40,8 +41,8 @@ func ListScheduledTasks(c *gin.Context) {
|
||||
// Enrich with instance_name and instance_db_type
|
||||
type ScheduledTicketItem struct {
|
||||
model.Ticket
|
||||
InstanceName string `json:"instance_name"`
|
||||
InstanceDBType string `json:"instance_db_type"`
|
||||
InstanceName string `json:"instance_name"`
|
||||
InstanceDBType string `json:"instance_db_type"`
|
||||
}
|
||||
|
||||
items := make([]ScheduledTicketItem, 0, len(tickets))
|
||||
@@ -71,19 +72,19 @@ func CancelScheduledTask(c *gin.Context) {
|
||||
|
||||
var ticket model.Ticket
|
||||
if err := model.DB.First(&ticket, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"})
|
||||
resp.NotFound(c, "Ticket not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Only the applicant or admin/dba can cancel
|
||||
if ticket.ApplicantID != userID && role != "admin" && role != "dba" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"detail": "Permission denied"})
|
||||
resp.Forbidden(c, "Permission denied")
|
||||
return
|
||||
}
|
||||
|
||||
// Only allow cancellation of scheduled tickets
|
||||
if !ticket.ScheduledAt.Valid {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "Ticket is not scheduled"})
|
||||
resp.BadRequest(c, "Ticket is not scheduled")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user