activity

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 12, 2026 License: MIT Imports: 10 Imported by: 5

README

Activity module

Audit logging helpers, a Bun-backed sink/repository, and query helpers for recent activity feeds and stats.

Components

  • BuildRecordFromActor and BuildRecordFromUUID convert request context into types.ActivityRecord with trimmed fields and cloned metadata.
  • Bun repository (activity.NewRepository) implements both types.ActivitySink and types.ActivityRepository for writes and reads.
  • Query handlers under query consume types.ActivityFilter/ActivityStatsFilter and respect tenant/org scope.

Constructing records

Use BuildRecordFromActor when you have a go-auth ActorContext (HTTP middleware); use BuildRecordFromUUID when you only have an actor UUID (background jobs, message handlers).

// From a request with go-auth metadata.
rec, err := activity.BuildRecordFromActor(actorCtx,
    "settings.updated",
    "settings",
    "global",
    map[string]any{"path": "ui.theme", "from": "light", "to": "dark"},
    activity.WithChannel("settings"),
)

// From a background worker with only an actor UUID.
rec, err := activity.BuildRecordFromUUID(actorID,
    "export.completed",
    "export.job",
    jobID,
    map[string]any{"format": "csv", "count": 120},
    activity.WithTenant(tenantID),
    activity.WithOrg(orgID),
    activity.WithOccurredAt(startedAt),
)

Record options:

  • WithChannel(string): module-level filter tag.
  • WithTenant(uuid.UUID): override tenant scope when not present in the actor context.
  • WithOrg(uuid.UUID): override org scope.
  • WithOccurredAt(time.Time): set a deterministic timestamp (defaults to time.Now().UTC()).

Wiring the sink/repository

store, err := activity.NewRepository(activity.RepositoryConfig{
    DB:    bunDB,
    Clock: types.SystemClock{},
    IDGen: types.UUIDGenerator{},
})
if err != nil {
    return err
}

svc := users.New(users.Config{
    ActivitySink:       store,
    ActivityRepository: store,
    // other dependencies...
})

if err := svc.ActivitySink.Log(ctx, rec); err != nil {
    return err
}

Log fills ID/OccurredAt when missing and persists to the user_activity table created by migration 000003_user_activity.sql.

Queries

feed, _ := svc.Queries().ActivityFeed.Query(ctx, types.ActivityFilter{
    Scope:      types.ScopeFilter{TenantID: tenantID},
    Channel:    "settings",
    Pagination: types.Pagination{Limit: 20},
})

stats, _ := svc.Queries().ActivityStats.Query(ctx, types.ActivityStatsFilter{
    Scope: types.ScopeFilter{TenantID: tenantID},
})

Filters include optional ActorID, UserID, ObjectType, ObjectID, Verb, Channel, Since, and Until.

Conventions

  • Verbs/objects: settings.updated (settings), export.completed (export.job), bulk.users.updated (bulk.job), media.uploaded (media.asset).
  • Channels: lowercase module names (settings, export, bulk, media) for dashboard filtering.
  • Metadata: flat, JSON-serializable keys; include counts and scope hints when relevant.

See docs/ACTIVITY.md for deeper guidance, indexes, and schema details.

Documentation

Overview

Package activity provides default persistence helpers for the go-users ActivitySink. The Repository implements both the sink (writes) and the ActivityRepository read-side contract so transports can log lifecycle events and later query them for dashboards. The ActivitySink interface lives in pkg/types and is intentionally minimal (`Log(ctx, ActivityRecord) error`) so hosts can swap sinks without breaking changes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildRecordFromActor

func BuildRecordFromActor(actor *auth.ActorContext, verb, objectType, objectID string, metadata map[string]any, opts ...RecordOption) (types.ActivityRecord, error)

BuildRecordFromActor constructs an ActivityRecord using the actor metadata supplied by go-auth middleware plus verb/object details and optional metadata. It normalizes actor, tenant, and org identifiers into UUIDs and defensively copies metadata to avoid caller mutation.

func BuildRecordFromUUID added in v0.3.0

func BuildRecordFromUUID(actorID uuid.UUID, verb, objectType, objectID string, metadata map[string]any, opts ...RecordOption) (types.ActivityRecord, error)

BuildRecordFromUUID constructs an ActivityRecord when only the actor UUID is available. It trims verb/object fields, validates required values, copies metadata defensively, and applies RecordOptions.

func ToActivityRecord

func ToActivityRecord(entry *LogEntry) types.ActivityRecord

ToActivityRecord converts the Bun model into the domain activity record.

Types

type LogEntry

type LogEntry struct {
	bun.BaseModel `bun:"table:user_activity"`

	ID         uuid.UUID      `bun:",pk,type:uuid"`
	UserID     uuid.UUID      `bun:"user_id,type:uuid"`
	ActorID    uuid.UUID      `bun:"actor_id,type:uuid"`
	TenantID   uuid.UUID      `bun:"tenant_id,type:uuid"`
	OrgID      uuid.UUID      `bun:"org_id,type:uuid"`
	Verb       string         `bun:"verb"`
	ObjectType string         `bun:"object_type"`
	ObjectID   string         `bun:"object_id"`
	Channel    string         `bun:"channel"`
	IP         string         `bun:"ip"`
	Data       map[string]any `bun:"data,type:jsonb"`
	CreatedAt  time.Time      `bun:"created_at"`
}

LogEntry models the persisted row in user_activity.

func FromActivityRecord

func FromActivityRecord(record types.ActivityRecord) *LogEntry

FromActivityRecord converts a domain activity record into the Bun model so it can be reused by transports without duplicating conversion logic.

type RecordOption

type RecordOption func(*types.ActivityRecord)

RecordOption mutates the ActivityRecord produced by BuildRecordFromActor.

func WithChannel

func WithChannel(channel string) RecordOption

WithChannel sets the channel/module field used for downstream filtering.

func WithOccurredAt added in v0.3.0

func WithOccurredAt(occurredAt time.Time) RecordOption

WithOccurredAt overrides the default occurrence timestamp.

func WithOrg added in v0.3.0

func WithOrg(orgID uuid.UUID) RecordOption

WithOrg sets the organization identifier for the record.

func WithTenant added in v0.3.0

func WithTenant(tenantID uuid.UUID) RecordOption

WithTenant sets the tenant identifier for the record.

type Repository

type Repository struct {
	// contains filtered or unexported fields
}

Repository persists activity logs and exposes query helpers.

func NewRepository

func NewRepository(cfg RepositoryConfig) (*Repository, error)

NewRepository constructs a repository that implements both ActivitySink and ActivityRepository interfaces.

func (*Repository) ActivityStats

func (r *Repository) ActivityStats(ctx context.Context, filter types.ActivityStatsFilter) (types.ActivityStats, error)

ActivityStats aggregates counts grouped by verb.

func (*Repository) ListActivity

func (r *Repository) ListActivity(ctx context.Context, filter types.ActivityFilter) (types.ActivityPage, error)

ListActivity returns a paginated feed filtered by the supplied criteria.

func (*Repository) Log

func (r *Repository) Log(ctx context.Context, record types.ActivityRecord) error

Log persists an activity record into the database.

type RepositoryConfig

type RepositoryConfig struct {
	DB         *bun.DB
	Repository repository.Repository[*LogEntry]
	Clock      types.Clock
	IDGen      types.IDGenerator
}

RepositoryConfig wires the Bun-backed activity repository.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL