Raw
  1package storage
  2
  3import (
  4	"context"
  5	"database/sql"
  6	"fmt"
  7	"strings"
  8	"time"
  9)
 10
 11func (s *Store) AppendAudit(ctx context.Context, input AppendAuditInput) (AuditEvent, error) {
 12	const operation = "append audit event"
 13	if err := s.checkOpen(operation); err != nil {
 14		return AuditEvent{}, err
 15	}
 16	return appendAudit(ctx, s.sql, operation, input, canonicalTime(s.now()))
 17}
 18
 19func appendAudit(ctx context.Context, q execer, operation string, input AppendAuditInput, occurredAt time.Time) (AuditEvent, error) {
 20	if err := requireValue("actor", input.Actor); err != nil {
 21		return AuditEvent{}, invalid(operation, err)
 22	}
 23	if err := requireValue("action", input.Action); err != nil {
 24		return AuditEvent{}, invalid(operation, err)
 25	}
 26	if err := requireValue("subject", input.Subject); err != nil {
 27		return AuditEvent{}, invalid(operation, err)
 28	}
 29	address := auditAddress(ctx, input.Address)
 30	detail := input.Detail
 31	if detail == "" {
 32		detail = "{}"
 33	}
 34	result, err := q.ExecContext(ctx, `
 35INSERT INTO audit_log (actor, action, subject, occurred_at, detail, address)
 36VALUES (?, ?, ?, ?, ?, ?)`, input.Actor, input.Action, input.Subject,
 37		timeToInt(occurredAt), detail, nullAddress(address))
 38	if err != nil {
 39		return AuditEvent{}, classify(operation, err)
 40	}
 41	id, err := result.LastInsertId()
 42	if err != nil {
 43		return AuditEvent{}, classify(operation+" id", err)
 44	}
 45	return AuditEvent{ID: id, Actor: input.Actor, Action: input.Action, Subject: input.Subject, OccurredAt: occurredAt, Detail: detail, Address: address}, nil
 46}
 47
 48func (s *Store) ListAudit(ctx context.Context, filter AuditFilter) ([]AuditEvent, error) {
 49	const operation = "list audit events"
 50	if err := s.checkOpen(operation); err != nil {
 51		return nil, err
 52	}
 53	limit := filter.Limit
 54	if limit == 0 {
 55		limit = 100
 56	}
 57	if limit < 1 || limit > 1000 {
 58		return nil, invalid(operation, fmt.Errorf("limit must be between 1 and 1000"))
 59	}
 60	query := strings.Builder{}
 61	query.WriteString(`SELECT id, actor, action, subject, occurred_at, detail, address FROM audit_log WHERE 1 = 1`)
 62	var args []any
 63	if filter.Actor != "" {
 64		query.WriteString(` AND actor = ?`)
 65		args = append(args, filter.Actor)
 66	}
 67	if filter.Action != "" {
 68		query.WriteString(` AND action = ?`)
 69		args = append(args, filter.Action)
 70	}
 71	if filter.Subject != "" {
 72		query.WriteString(` AND subject = ?`)
 73		args = append(args, filter.Subject)
 74	}
 75	if filter.After != nil {
 76		query.WriteString(` AND occurred_at >= ?`)
 77		args = append(args, timeToInt(*filter.After))
 78	}
 79	if filter.Before != nil {
 80		query.WriteString(` AND occurred_at < ?`)
 81		args = append(args, timeToInt(*filter.Before))
 82	}
 83	query.WriteString(` ORDER BY occurred_at DESC, id DESC LIMIT ?`)
 84	args = append(args, limit)
 85	rows, err := s.sql.QueryContext(ctx, query.String(), args...)
 86	if err != nil {
 87		return nil, classify(operation, err)
 88	}
 89	defer rows.Close()
 90	var events []AuditEvent
 91	for rows.Next() {
 92		var event AuditEvent
 93		var occurredAt int64
 94		var address sql.NullString
 95		if err := rows.Scan(&event.ID, &event.Actor, &event.Action, &event.Subject, &occurredAt, &event.Detail, &address); err != nil {
 96			return nil, classify(operation, err)
 97		}
 98		event.OccurredAt = intToTime(occurredAt)
 99		event.Address = scanAddress(address)
100		events = append(events, event)
101	}
102	if err := rows.Err(); err != nil {
103		return nil, classify(operation, err)
104	}
105	return events, nil
106}