package middleware import ( "bytes" "context" "encoding/json" "io" "time" "github.com/gin-gonic/gin" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/repository" ) type OperationLogMiddleware struct { repo *repository.OperationLogRepository } func NewOperationLogMiddleware(repo *repository.OperationLogRepository) *OperationLogMiddleware { return &OperationLogMiddleware{repo: repo} } type bodyWriter struct { gin.ResponseWriter statusCode int } func newBodyWriter(w gin.ResponseWriter) *bodyWriter { return &bodyWriter{ResponseWriter: w, statusCode: 200} } func (bw *bodyWriter) WriteHeader(code int) { bw.statusCode = code bw.ResponseWriter.WriteHeader(code) } func (bw *bodyWriter) WriteHeaderNow() { bw.ResponseWriter.WriteHeaderNow() } func (m *OperationLogMiddleware) Record() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method if method == "GET" || method == "HEAD" || method == "OPTIONS" { c.Next() return } var reqParams string if c.Request.Body != nil { bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 4096)) if err == nil { c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) reqParams = sanitizeParams(bodyBytes) } } bw := newBodyWriter(c.Writer) c.Writer = bw c.Next() var userIDPtr *int64 if uid, exists := c.Get("user_id"); exists { if id, ok := uid.(int64); ok { userID := id userIDPtr = &userID } } logEntry := &domain.OperationLog{ UserID: userIDPtr, OperationType: methodToType(method), OperationName: c.FullPath(), RequestMethod: method, RequestPath: c.Request.URL.Path, RequestParams: reqParams, ResponseStatus: bw.statusCode, IP: c.ClientIP(), UserAgent: c.Request.UserAgent(), } go func(entry *domain.OperationLog) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() _ = m.repo.Create(ctx, entry) }(logEntry) } } func methodToType(method string) string { switch method { case "POST": return "CREATE" case "PUT", "PATCH": return "UPDATE" case "DELETE": return "DELETE" default: return "OTHER" } } func sanitizeParams(data []byte) string { var payload map[string]interface{} if err := json.Unmarshal(data, &payload); err != nil { if len(data) > 500 { return string(data[:500]) + "..." } return string(data) } for _, field := range []string{"password", "old_password", "new_password", "confirm_password", "secret", "token"} { if _, ok := payload[field]; ok { payload[field] = "***" } } result, err := json.Marshal(payload) if err != nil { return "" } return string(result) }