package model import ( "encoding/json" "strings" "time" "gorm.io/gorm" ) // AuditLog represents an audit log entry in the system type AuditLog struct { BaseModel UserID string `json:"userId" gorm:"type:varchar(36);index"` Action string `json:"action" gorm:"not null;type:varchar(100);index"` Resource string `json:"resource" gorm:"not null;type:varchar(100);index"` ResourceID string `json:"resourceId" gorm:"type:varchar(36);index"` Details map[string]interface{} `json:"details" gorm:"type:text"` IPAddress string `json:"ipAddress" gorm:"type:varchar(45)"` UserAgent string `json:"userAgent" gorm:"type:text"` Success bool `json:"success" gorm:"default:true;index"` ErrorMsg string `json:"errorMsg,omitempty" gorm:"type:text"` Duration int64 `json:"duration,omitempty"` // Duration in milliseconds SessionID string `json:"sessionId,omitempty" gorm:"type:varchar(255)"` RequestID string `json:"requestId,omitempty" gorm:"type:varchar(255)"` User *User `json:"user,omitempty" gorm:"foreignKey:UserID"` } // AuditLogCreateRequest represents the request to create a new audit log type AuditLogCreateRequest struct { UserID string `json:"userId"` Action string `json:"action" validate:"required,max=100"` Resource string `json:"resource" validate:"required,max=100"` ResourceID string `json:"resourceId"` Details map[string]interface{} `json:"details"` IPAddress string `json:"ipAddress" validate:"max=45"` UserAgent string `json:"userAgent"` Success bool `json:"success"` ErrorMsg string `json:"errorMsg"` Duration int64 `json:"duration"` SessionID string `json:"sessionId"` RequestID string `json:"requestId"` } // AuditLogInfo represents public audit log information type AuditLogInfo struct { ID string `json:"id"` UserID string `json:"userId"` UserEmail string `json:"userEmail,omitempty"` UserName string `json:"userName,omitempty"` Action string `json:"action"` Resource string `json:"resource"` ResourceID string `json:"resourceId"` Details map[string]interface{} `json:"details"` IPAddress string `json:"ipAddress"` UserAgent string `json:"userAgent"` Success bool `json:"success"` ErrorMsg string `json:"errorMsg,omitempty"` Duration int64 `json:"duration,omitempty"` SessionID string `json:"sessionId,omitempty"` RequestID string `json:"requestId,omitempty"` DateCreated string `json:"dateCreated"` } // BeforeCreate is called before creating an audit log func (al *AuditLog) BeforeCreate(tx *gorm.DB) error { al.BaseModel.BeforeCreate() // Normalize fields al.Action = strings.ToLower(strings.TrimSpace(al.Action)) al.Resource = strings.ToLower(strings.TrimSpace(al.Resource)) al.IPAddress = strings.TrimSpace(al.IPAddress) al.UserAgent = strings.TrimSpace(al.UserAgent) return nil } // SetDetails sets the details field from a map func (al *AuditLog) SetDetails(details map[string]interface{}) error { al.Details = details return nil } // GetDetails returns the details as a map func (al *AuditLog) GetDetails() map[string]interface{} { if al.Details == nil { return make(map[string]interface{}) } return al.Details } // SetDetailsFromJSON sets the details field from a JSON string func (al *AuditLog) SetDetailsFromJSON(jsonStr string) error { if jsonStr == "" { al.Details = make(map[string]interface{}) return nil } var details map[string]interface{} if err := json.Unmarshal([]byte(jsonStr), &details); err != nil { return err } al.Details = details return nil } // GetDetailsAsJSON returns the details as a JSON string func (al *AuditLog) GetDetailsAsJSON() (string, error) { if al.Details == nil || len(al.Details) == 0 { return "{}", nil } bytes, err := json.Marshal(al.Details) if err != nil { return "", err } return string(bytes), nil } // ToAuditLogInfo converts AuditLog to AuditLogInfo (public information) func (al *AuditLog) ToAuditLogInfo() AuditLogInfo { info := AuditLogInfo{ ID: al.ID, UserID: al.UserID, Action: al.Action, Resource: al.Resource, ResourceID: al.ResourceID, Details: al.GetDetails(), IPAddress: al.IPAddress, UserAgent: al.UserAgent, Success: al.Success, ErrorMsg: al.ErrorMsg, Duration: al.Duration, SessionID: al.SessionID, RequestID: al.RequestID, DateCreated: al.DateCreated.Format("2006-01-02T15:04:05Z"), } // Include user information if available if al.User != nil { info.UserEmail = al.User.Email info.UserName = al.User.Name } return info } // AddDetail adds a single detail to the details map func (al *AuditLog) AddDetail(key string, value interface{}) { if al.Details == nil { al.Details = make(map[string]interface{}) } al.Details[key] = value } // GetDetail gets a single detail from the details map func (al *AuditLog) GetDetail(key string) (interface{}, bool) { if al.Details == nil { return nil, false } value, exists := al.Details[key] return value, exists } // Common audit log actions const ( AuditActionCreate = "create" AuditActionRead = "read" AuditActionUpdate = "update" AuditActionDelete = "delete" AuditActionLogin = "login" AuditActionLogout = "logout" AuditActionAccess = "access" AuditActionExport = "export" AuditActionImport = "import" AuditActionConfig = "config" ) // Common audit log resources const ( AuditResourceUser = "user" AuditResourceRole = "role" AuditResourcePermission = "permission" AuditResourceSystemConfig = "system_config" AuditResourceAuth = "auth" AuditResourceAPI = "api" AuditResourceFile = "file" AuditResourceDatabase = "database" AuditResourceSystem = "system" ) // CreateAuditLog creates a new audit log entry func CreateAuditLog(userID, action, resource, resourceID string, success bool) *AuditLog { auditLog := &AuditLog{ UserID: userID, Action: action, Resource: resource, ResourceID: resourceID, Success: success, Details: make(map[string]interface{}), } auditLog.Init() return auditLog } // CreateAuditLogWithDetails creates a new audit log entry with details func CreateAuditLogWithDetails(userID, action, resource, resourceID string, success bool, details map[string]interface{}) *AuditLog { auditLog := CreateAuditLog(userID, action, resource, resourceID, success) auditLog.Details = details return auditLog } // CreateAuditLogWithError creates a new audit log entry for an error func CreateAuditLogWithError(userID, action, resource, resourceID, errorMsg string) *AuditLog { auditLog := CreateAuditLog(userID, action, resource, resourceID, false) auditLog.ErrorMsg = errorMsg return auditLog } // SetRequestInfo sets request-related information func (al *AuditLog) SetRequestInfo(ipAddress, userAgent, sessionID, requestID string) { al.IPAddress = ipAddress al.UserAgent = userAgent al.SessionID = sessionID al.RequestID = requestID } // SetDuration sets the operation duration func (al *AuditLog) SetDuration(start time.Time) { al.Duration = time.Since(start).Milliseconds() } // IsSuccess returns whether the audit log represents a successful operation func (al *AuditLog) IsSuccess() bool { return al.Success } // IsFailure returns whether the audit log represents a failed operation func (al *AuditLog) IsFailure() bool { return !al.Success } // GetActionDescription returns a human-readable description of the action func (al *AuditLog) GetActionDescription() string { switch al.Action { case AuditActionCreate: return "Created " + al.Resource case AuditActionRead: return "Viewed " + al.Resource case AuditActionUpdate: return "Updated " + al.Resource case AuditActionDelete: return "Deleted " + al.Resource case AuditActionLogin: return "Logged in" case AuditActionLogout: return "Logged out" case AuditActionAccess: return "Accessed " + al.Resource case AuditActionExport: return "Exported " + al.Resource case AuditActionImport: return "Imported " + al.Resource case AuditActionConfig: return "Configured " + al.Resource default: return strings.Title(al.Action) + " " + al.Resource } }