package db import ( "context" "database/sql" "encoding/json" "github.com/pkg/errors" "github.com/uptrace/bun" ) type AuditLog struct { bun.BaseModel `bun:"table:audit_log,alias:al"` ID int `bun:"id,pk,autoincrement"` UserID int `bun:"user_id,notnull"` Action string `bun:"action,notnull"` ResourceType string `bun:"resource_type,notnull"` ResourceID *string `bun:"resource_id"` Details json.RawMessage `bun:"details,type:jsonb"` IPAddress string `bun:"ip_address"` UserAgent string `bun:"user_agent"` Result string `bun:"result,notnull"` // success, denied, error ErrorMessage *string `bun:"error_message"` CreatedAt int64 `bun:"created_at,notnull"` // Relations User *User `bun:"rel:belongs-to,join:user_id=id"` } type AuditLogs struct { AuditLogs []*AuditLog Total int PageOpts PageOpts } // CreateAuditLog creates a new audit log entry func CreateAuditLog(ctx context.Context, tx bun.Tx, log *AuditLog) error { if log == nil { return errors.New("log cannot be nil") } _, err := tx.NewInsert(). Model(log). Exec(ctx) if err != nil { return errors.Wrap(err, "tx.NewInsert") } return nil } type AuditLogFilters struct { UserID *int Action *string ResourceType *string Result *string } // GetAuditLogs retrieves audit logs with optional filters and pagination func GetAuditLogs(ctx context.Context, tx bun.Tx, pageOpts *PageOpts, filters *AuditLogFilters) (*AuditLogs, error) { query := tx.NewSelect(). Model((*AuditLog)(nil)). Relation("User") // Apply filters if provided if filters != nil { if filters.UserID != nil { query = query.Where("al.user_id = ?", *filters.UserID) } if filters.Action != nil { query = query.Where("al.action = ?", *filters.Action) } if filters.ResourceType != nil { query = query.Where("al.resource_type = ?", *filters.ResourceType) } if filters.Result != nil { query = query.Where("al.result = ?", *filters.Result) } } // Get total count total, err := query.Count(ctx) if err != nil { return nil, errors.Wrap(err, "query.Count") } // Get paginated results query, pageOpts = setPageOpts(query, pageOpts, 1, 50, bun.OrderDesc, "created_at") logs := new([]*AuditLog) err = query.Scan(ctx, &logs) if err != nil && err != sql.ErrNoRows { return nil, errors.Wrap(err, "query.Scan") } list := &AuditLogs{ AuditLogs: *logs, Total: total, PageOpts: *pageOpts, } return list, nil } // GetAuditLogsByUser retrieves audit logs for a specific user func GetAuditLogsByUser(ctx context.Context, tx bun.Tx, userID int, pageOpts *PageOpts) (*AuditLogs, error) { if userID <= 0 { return nil, errors.New("userID must be positive") } filters := &AuditLogFilters{ UserID: &userID, } return GetAuditLogs(ctx, tx, pageOpts, filters) } // GetAuditLogsByAction retrieves audit logs for a specific action func GetAuditLogsByAction(ctx context.Context, tx bun.Tx, action string, pageOpts *PageOpts) (*AuditLogs, error) { if action == "" { return nil, errors.New("action cannot be empty") } filters := &AuditLogFilters{ Action: &action, } return GetAuditLogs(ctx, tx, pageOpts, filters) } // CleanupOldAuditLogs deletes audit logs older than the specified timestamp func CleanupOldAuditLogs(ctx context.Context, tx bun.Tx, olderThan int64) (int, error) { result, err := tx.NewDelete(). Model((*AuditLog)(nil)). Where("created_at < ?", olderThan). Exec(ctx) if err != nil { return 0, errors.Wrap(err, "tx.NewDelete") } rowsAffected, err := result.RowsAffected() if err != nil { return 0, errors.Wrap(err, "result.RowsAffected") } return int(rowsAffected), nil }