package db import ( "context" "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"` } // 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 := Insert(tx, log).Exec(ctx) if err != nil { return errors.Wrap(err, "db.Insert") } return nil } type AuditLogFilter struct { *ListFilter } func NewAuditLogFilter() *AuditLogFilter { return &AuditLogFilter{NewListFilter()} } func (a *AuditLogFilter) UserID(id int) *AuditLogFilter { a.Add("al.user_id", id) return a } func (a *AuditLogFilter) Action(action string) *AuditLogFilter { a.Add("al.action", action) return a } func (a *AuditLogFilter) ResourceType(resourceType string) *AuditLogFilter { a.Add("al.resource_type", resourceType) return a } func (a *AuditLogFilter) Result(result string) *AuditLogFilter { a.Add("al.result", result) return a } // GetAuditLogs retrieves audit logs with optional filters and pagination func GetAuditLogs(ctx context.Context, tx bun.Tx, pageOpts *PageOpts, filters *AuditLogFilter) (*List[AuditLog], error) { defaultPageOpts := &PageOpts{ Page: 1, PerPage: 50, Order: bun.OrderDesc, OrderBy: "created_at", } return GetList[AuditLog](tx). Relation("User"). Filter(filters.filters...). GetPaged(ctx, pageOpts, defaultPageOpts) } // GetAuditLogsByUser retrieves audit logs for a specific user func GetAuditLogsByUser(ctx context.Context, tx bun.Tx, userID int, pageOpts *PageOpts) (*List[AuditLog], error) { if userID <= 0 { return nil, errors.New("userID must be positive") } filters := NewAuditLogFilter().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) (*List[AuditLog], error) { if action == "" { return nil, errors.New("action cannot be empty") } filters := NewAuditLogFilter().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 }