package admin import ( "encoding/json" "io" "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/gothinkster/golang-gin-realworld-example-app/common" "github.com/gothinkster/golang-gin-realworld-example-app/middleware" "github.com/gothinkster/golang-gin-realworld-example-app/models" ) // PaginationParams holds parsed ProTable pagination query params type PaginationParams struct { Current int PageSize int } // ParsePagination reads ?current=N&pageSize=M from the request func ParsePagination(c *gin.Context) PaginationParams { current, _ := strconv.Atoi(c.DefaultQuery("current", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) if current < 1 { current = 1 } if pageSize < 1 { pageSize = 20 } if pageSize > 100 { pageSize = 100 } return PaginationParams{Current: current, PageSize: pageSize} } // Offset returns the DB offset for GORM queries func (p PaginationParams) Offset() int { return (p.Current - 1) * p.PageSize } // OK responds with {success: true, data: ...} func OK(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, gin.H{"success": true, "data": data}) } // OKList responds with {success: true, data: [...], total: N} — used by ProTable func OKList(c *gin.Context, data interface{}, total int64) { c.JSON(http.StatusOK, gin.H{"success": true, "data": data, "total": total}) } // Fail responds with {success: false, message: ...} func Fail(c *gin.Context, status int, msg string) { c.JSON(status, gin.H{"success": false, "message": msg}) } // flexTimeLayouts is the set of time formats accepted from the frontend. var flexTimeLayouts = []string{ "2006-01-02T15:04:05", "2006-01-02 15:04:05", "2006-01-02T15:04", "2006-01-02 15:04", "2006-01-02", } // BindJSONFlexTime works like c.ShouldBindJSON but also accepts non-RFC3339 // time strings such as "2026-02-28 00:00:00" that Ant Design's DatePicker sends. // It reads the raw body, converts any date-like strings to RFC3339, then unmarshals. func BindJSONFlexTime(c *gin.Context, obj interface{}) error { body, err := io.ReadAll(c.Request.Body) if err != nil { return err } var raw map[string]json.RawMessage if err := json.Unmarshal(body, &raw); err != nil { return json.Unmarshal(body, obj) // fallback: let normal errors surface } for key, val := range raw { var s string if err := json.Unmarshal(val, &s); err != nil { continue // not a string, skip } for _, layout := range flexTimeLayouts { if t, err := time.Parse(layout, s); err == nil { converted, _ := json.Marshal(t) // produces RFC3339 raw[key] = converted break } } } fixed, err := json.Marshal(raw) if err != nil { return err } return json.Unmarshal(fixed, obj) } // LogOp writes a record to operation_logs table func LogOp(c *gin.Context, opType, targetType, action string, targetID *uint, payload interface{}) { db := common.GetDB() user, _ := middleware.GetCurrentUser(c) var userID *uint if user != nil { uid := uint(user.UserID) userID = &uid } var changesStr string if payload != nil { b, _ := json.Marshal(payload) changesStr = string(b) } db.Create(&models.OperationLog{ UserID: userID, OperationType: opType, TargetType: targetType, Action: action, TargetID: targetID, Changes: changesStr, IPAddress: c.ClientIP(), UserAgent: c.GetHeader("User-Agent"), Status: "success", CreatedAt: time.Now(), }) }