198 lines
5.1 KiB
Go
198 lines
5.1 KiB
Go
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
|
|
customWhere []whereClause
|
|
}
|
|
|
|
type whereClause struct {
|
|
query string
|
|
args []any
|
|
}
|
|
|
|
func NewAuditLogFilter() *AuditLogFilter {
|
|
return &AuditLogFilter{
|
|
ListFilter: NewListFilter(),
|
|
customWhere: []whereClause{},
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (a *AuditLogFilter) DateRange(start, end int64) *AuditLogFilter {
|
|
if start > 0 {
|
|
a.Add("al.created_at", ">=", start)
|
|
}
|
|
if end > 0 {
|
|
a.Add("al.created_at", "<=", end)
|
|
}
|
|
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",
|
|
}
|
|
|
|
lg := GetList[AuditLog](tx).
|
|
Relation("User").
|
|
Filter(filters.filters...)
|
|
|
|
// Apply custom where clauses (e.g., date range)
|
|
for _, clause := range filters.customWhere {
|
|
lg = lg.Where(clause.query, clause.args...)
|
|
}
|
|
|
|
return lg.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)
|
|
}
|
|
|
|
// GetAuditLogByID retrieves a single audit log by ID
|
|
func GetAuditLogByID(ctx context.Context, tx bun.Tx, id int) (*AuditLog, error) {
|
|
if id <= 0 {
|
|
return nil, errors.New("id must be positive")
|
|
}
|
|
|
|
log := new(AuditLog)
|
|
err := tx.NewSelect().
|
|
Model(log).
|
|
Relation("User").
|
|
Where("al.id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
if err.Error() == "sql: no rows in result set" {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return log, nil
|
|
}
|
|
|
|
// GetUniqueActions retrieves a list of all unique actions in the audit log
|
|
func GetUniqueActions(ctx context.Context, tx bun.Tx) ([]string, error) {
|
|
var actions []string
|
|
err := tx.NewSelect().
|
|
Model((*AuditLog)(nil)).
|
|
Column("action").
|
|
Distinct().
|
|
Order("action ASC").
|
|
Scan(ctx, &actions)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return actions, nil
|
|
}
|
|
|
|
// GetUniqueResourceTypes retrieves a list of all unique resource types in the audit log
|
|
func GetUniqueResourceTypes(ctx context.Context, tx bun.Tx) ([]string, error) {
|
|
var resourceTypes []string
|
|
err := tx.NewSelect().
|
|
Model((*AuditLog)(nil)).
|
|
Column("resource_type").
|
|
Distinct().
|
|
Order("resource_type ASC").
|
|
Scan(ctx, &resourceTypes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return resourceTypes, nil
|
|
}
|
|
|
|
// 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
|
|
}
|