Files
omega-server/local/model/audit_log.go
Fran Jurmanović 016728532c init bootstrap
2025-07-06 15:02:09 +02:00

272 lines
8.6 KiB
Go

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
}
}