activity

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 14 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, Channels, ChannelDenylist, Since, and Until.

Role-aware filtering & sanitization

Use BuildFilterFromActor for role-aware filters or attach the default access policy to activity queries:

policy := activity.NewDefaultAccessPolicy(
    activity.WithPolicyFilterOptions(
        activity.WithChannelAllowlist("settings", "roles"),
        activity.WithMachineActivityEnabled(false),
        activity.WithSuperadminScope(true),
    ),
)

feedQuery := query.NewActivityFeedQuery(store, scopeGuard, query.WithActivityAccessPolicy(policy))
feed, _ := feedQuery.Query(ctx, types.ActivityFilter{
    Actor:      actorRef,
    Pagination: types.Pagination{Limit: 25},
})

Defaults treat system_admin/superadmin as superadmins and tenant_admin/admin/org_admin as admins; you can override with WithRoleAliases. Sanitization uses go-masker defaults and redacts IPs for non-superadmins by default.

Optional cursor pagination

For high-volume feeds, the cursor helper can paginate by created_at and id:

cursor := &activity.ActivityCursor{
    OccurredAt: lastRecord.OccurredAt,
    ID:         lastRecord.ID,
}
query := activity.ApplyCursorPagination(db.NewSelect().Model(&rows), cursor, 50)

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

View Source
const (
	DataKeyActorDisplay    = "actor_display"
	DataKeyActorEmail      = "actor_email"
	DataKeyActorID         = "actor_id"
	DataKeyActorType       = "actor_type"
	DataKeyObjectDisplay   = "object_display"
	DataKeyObjectType      = "object_type"
	DataKeyObjectID        = "object_id"
	DataKeyObjectDeleted   = "object_deleted"
	DataKeySessionID       = "session_id"
	DataKeyEnrichedAt      = "enriched_at"
	DataKeyEnricherVersion = "enricher_version"
)

Enrichment metadata keys. These are flat JSONB keys and should remain stable.

View Source
const DefaultEnricherVersion = "v1"

DefaultEnricherVersion is the built-in fallback version string when enrichment is enabled.

Variables

View Source
var (
	// ErrMissingActivityMapperRegistry indicates registry-backed mapper has no registry.
	ErrMissingActivityMapperRegistry = errors.New("go-users: missing activity mapper registry")
	// ErrActivityMapperNameRequired indicates mapper registration/query lacked a name.
	ErrActivityMapperNameRequired = errors.New("go-users: activity mapper name required")
	// ErrActivityMapperExists indicates the mapper name is already registered.
	ErrActivityMapperExists = errors.New("go-users: activity mapper already registered")
	// ErrActivityMapperNotFound indicates the named mapper is not registered.
	ErrActivityMapperNotFound = errors.New("go-users: activity mapper not found")
)
View Source
var (
	// ErrMissingActivityMapper indicates hook execution lacks a configured mapper.
	ErrMissingActivityMapper = errors.New("go-users: missing activity event mapper")
	// ErrUnsupportedActivityEvent indicates the mapper cannot interpret the event.
	ErrUnsupportedActivityEvent = errors.New("go-users: unsupported activity event")
)

Functions

func ApplyCursorPagination added in v0.10.0

func ApplyCursorPagination(q *bun.SelectQuery, cursor *ActivityCursor, limit int) *bun.SelectQuery

ApplyCursorPagination applies cursor pagination using created_at/id ordering. Results are ordered by created_at DESC, id DESC, and filtered to items older than the supplied cursor.

func AttachSessionID added in v0.14.0

func AttachSessionID(ctx context.Context, record types.ActivityRecord, provider SessionIDProvider, key string) types.ActivityRecord

AttachSessionID adds a session identifier to the record data if available.

func AttachSessionIDValue added in v0.14.0

func AttachSessionIDValue(record types.ActivityRecord, sessionID, key string) types.ActivityRecord

AttachSessionIDValue adds a session identifier to the record data if missing.

func BuildFilterFromActor added in v0.10.0

func BuildFilterFromActor(actor *auth.ActorContext, role string, req types.ActivityFilter, opts ...FilterOption) (types.ActivityFilter, error)

BuildFilterFromActor constructs a safe ActivityFilter using the auth actor context plus role-aware constraints and optional channel rules.

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 DefaultAdminRoleAliases added in v0.10.0

func DefaultAdminRoleAliases() []string

DefaultAdminRoleAliases returns the default admin role aliases.

func DefaultMachineActorTypes added in v0.10.0

func DefaultMachineActorTypes() []string

DefaultMachineActorTypes returns the default machine actor type identifiers.

func DefaultMachineDataKeys added in v0.10.0

func DefaultMachineDataKeys() []string

DefaultMachineDataKeys returns the default machine data keys.

func DefaultMasker added in v0.10.0

func DefaultMasker() *masker.Masker

DefaultMasker returns a configured masker instance with the default denylist.

func DefaultSuperadminRoleAliases added in v0.10.0

func DefaultSuperadminRoleAliases() []string

DefaultSuperadminRoleAliases returns the default superadmin role aliases.

func SanitizeRecord added in v0.10.0

func SanitizeRecord(mask *masker.Masker, record types.ActivityRecord) types.ActivityRecord

SanitizeRecord masks sensitive values in the activity record data payload.

func SanitizeRecords added in v0.10.0

func SanitizeRecords(mask *masker.Masker, records []types.ActivityRecord) []types.ActivityRecord

SanitizeRecords masks sensitive values for every record in the slice.

func StampEnrichment added in v0.14.0

func StampEnrichment(record types.ActivityRecord, now time.Time, version string) types.ActivityRecord

StampEnrichment sets enriched_at and enricher_version on the activity record.

func ToActivityRecord

func ToActivityRecord(entry *LogEntry) types.ActivityRecord

ToActivityRecord converts the Bun model into the domain activity record.

Types

type AccessPolicyOption added in v0.10.0

type AccessPolicyOption func(*DefaultAccessPolicy)

AccessPolicyOption customizes the default activity access policy.

func WithIPRedaction added in v0.10.0

func WithIPRedaction(enabled bool) AccessPolicyOption

WithIPRedaction toggles IP redaction for non-superadmin roles.

func WithMetadataExposure added in v0.15.0

func WithMetadataExposure(strategy MetadataExposureStrategy) AccessPolicyOption

WithMetadataExposure configures how activity metadata is exposed for support roles.

func WithMetadataSanitizer added in v0.15.0

func WithMetadataSanitizer(sanitizer MetadataSanitizer) AccessPolicyOption

WithMetadataSanitizer overrides the metadata sanitizer for sanitized exposure mode.

func WithPolicyFilterOptions added in v0.10.0

func WithPolicyFilterOptions(opts ...FilterOption) AccessPolicyOption

WithPolicyFilterOptions configures filter options applied during policy enforcement.

func WithPolicyMasker added in v0.10.0

func WithPolicyMasker(masker *masker.Masker) AccessPolicyOption

WithPolicyMasker overrides the masker used for sanitization.

func WithPolicyStatsSelfOnly added in v0.10.0

func WithPolicyStatsSelfOnly(enabled bool) AccessPolicyOption

WithPolicyStatsSelfOnly toggles self-only stats for non-admin roles.

type ActivityAccessPolicy added in v0.10.0

type ActivityAccessPolicy interface {
	Apply(actor *auth.ActorContext, role string, req types.ActivityFilter) (types.ActivityFilter, error)
	Sanitize(actor *auth.ActorContext, role string, records []types.ActivityRecord) []types.ActivityRecord
}

ActivityAccessPolicy applies role-aware constraints and sanitization to activity feeds.

type ActivityCursor added in v0.10.0

type ActivityCursor struct {
	OccurredAt time.Time
	ID         uuid.UUID
}

ActivityCursor defines the cursor shape for activity feeds.

type ActivityEnricher added in v0.14.0

type ActivityEnricher interface {
	Enrich(ctx context.Context, record types.ActivityRecord) (types.ActivityRecord, error)
}

ActivityEnricher mutates or returns an enriched ActivityRecord.

type ActivityEnrichmentFilter added in v0.14.0

type ActivityEnrichmentFilter struct {
	Scope          types.ScopeFilter
	MissingKeys    []string
	EnrichedBefore *time.Time
	Cursor         *ActivityCursor
	Limit          int
}

ActivityEnrichmentFilter narrows enrichment backfill selection.

type ActivityEnrichmentPage added in v0.14.0

type ActivityEnrichmentPage struct {
	Records    []types.ActivityRecord
	NextCursor *ActivityCursor
}

ActivityEnrichmentPage returns selected activity records and the next cursor.

type ActivityEnrichmentQuery added in v0.14.0

type ActivityEnrichmentQuery interface {
	ListActivityForEnrichment(ctx context.Context, filter ActivityEnrichmentFilter) (ActivityEnrichmentPage, error)
}

ActivityEnrichmentQuery exposes helper queries for missing/stale enrichment.

type ActivityEnrichmentStore added in v0.14.0

type ActivityEnrichmentStore interface {
	UpdateActivityData(ctx context.Context, id uuid.UUID, data map[string]any) error
}

ActivityEnrichmentStore updates activity metadata for backfill jobs.

type ActivityEnrichmentStoreWithOptions added in v0.14.0

type ActivityEnrichmentStoreWithOptions interface {
	UpdateActivityDataWithOptions(ctx context.Context, id uuid.UUID, data map[string]any, opts ActivityEnrichmentUpdateOptions) error
}

ActivityEnrichmentStoreWithOptions supports optional forced updates.

type ActivityEnrichmentUpdateOptions added in v0.14.0

type ActivityEnrichmentUpdateOptions struct {
	// ForceKeys allows overwriting specific keys (e.g., enriched_at) when needed.
	ForceKeys []string
}

ActivityEnrichmentUpdateOptions controls missing-key update behavior.

type ActivityEnvelope added in v0.17.0

type ActivityEnvelope struct {
	Channel    string
	Verb       string
	ObjectType string
	ObjectID   string
	ActorID    string
	TenantID   string
	OccurredAt time.Time
	Metadata   map[string]any
}

ActivityEnvelope is a generic envelope shape for low-coupling integrations.

func (ActivityEnvelope) ActivityActorID added in v0.17.0

func (e ActivityEnvelope) ActivityActorID() string

func (ActivityEnvelope) ActivityChannel added in v0.17.0

func (e ActivityEnvelope) ActivityChannel() string

func (ActivityEnvelope) ActivityMetadata added in v0.17.0

func (e ActivityEnvelope) ActivityMetadata() map[string]any

func (ActivityEnvelope) ActivityObjectID added in v0.17.0

func (e ActivityEnvelope) ActivityObjectID() string

func (ActivityEnvelope) ActivityObjectType added in v0.17.0

func (e ActivityEnvelope) ActivityObjectType() string

func (ActivityEnvelope) ActivityOccurredAt added in v0.17.0

func (e ActivityEnvelope) ActivityOccurredAt() time.Time

func (ActivityEnvelope) ActivityTenantID added in v0.17.0

func (e ActivityEnvelope) ActivityTenantID() string

func (ActivityEnvelope) ActivityVerb added in v0.17.0

func (e ActivityEnvelope) ActivityVerb() string

type ActivityEnvelopeCarrier added in v0.17.0

type ActivityEnvelopeCarrier interface {
	ActivityChannel() string
	ActivityVerb() string
	ActivityObjectType() string
	ActivityObjectID() string
	ActivityActorID() string
	ActivityTenantID() string
	ActivityOccurredAt() time.Time
	ActivityMetadata() map[string]any
}

ActivityEnvelopeCarrier exposes envelope fields used by EnvelopeMapper.

type ActivityEventMapper added in v0.17.0

type ActivityEventMapper interface {
	Map(ctx context.Context, evt any) (types.ActivityRecord, error)
}

ActivityEventMapper maps arbitrary events into ActivityRecord payloads.

type ActivityEventMapperFunc added in v0.17.0

type ActivityEventMapperFunc func(ctx context.Context, evt any) (types.ActivityRecord, error)

ActivityEventMapperFunc adapts a function into ActivityEventMapper.

func (ActivityEventMapperFunc) Map added in v0.17.0

Map executes the mapper function.

type ActivityMapperRegistry added in v0.17.0

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

ActivityMapperRegistry stores named event mappers for runtime composition.

func NewActivityMapperRegistry added in v0.17.0

func NewActivityMapperRegistry() *ActivityMapperRegistry

NewActivityMapperRegistry constructs an empty mapper registry.

func (*ActivityMapperRegistry) Lookup added in v0.17.0

Lookup returns the named mapper when present.

func (*ActivityMapperRegistry) Names added in v0.17.0

func (r *ActivityMapperRegistry) Names() []string

Names returns sorted mapper names.

func (*ActivityMapperRegistry) Register added in v0.17.0

func (r *ActivityMapperRegistry) Register(name string, mapper ActivityEventMapper) error

Register adds a named mapper. Names are normalized to lower-case.

type ActivityStatsPolicy added in v0.10.0

type ActivityStatsPolicy interface {
	ApplyStats(actor *auth.ActorContext, role string, req types.ActivityStatsFilter) (types.ActivityStatsFilter, error)
}

ActivityStatsPolicy applies role-aware constraints to activity stats.

type ActorInfo added in v0.14.0

type ActorInfo struct {
	ID      uuid.UUID
	Type    string
	Display string
	Email   string
}

ActorInfo defines enrichment details for an actor.

type ActorResolver added in v0.14.0

type ActorResolver interface {
	ResolveActors(ctx context.Context, ids []uuid.UUID, meta ResolveContext) (map[uuid.UUID]ActorInfo, error)
}

ActorResolver resolves actor enrichment details in batch.

type DefaultAccessPolicy added in v0.10.0

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

DefaultAccessPolicy applies BuildFilterFromActor and sanitizes records on read.

func NewDefaultAccessPolicy added in v0.10.0

func NewDefaultAccessPolicy(opts ...AccessPolicyOption) *DefaultAccessPolicy

NewDefaultAccessPolicy returns the default policy implementation.

func (*DefaultAccessPolicy) Apply added in v0.10.0

Apply enforces role-aware scope/visibility rules on the requested filter.

func (*DefaultAccessPolicy) ApplyStats added in v0.10.0

ApplyStats enforces role-aware scope/visibility rules on stats filters.

func (*DefaultAccessPolicy) Sanitize added in v0.10.0

func (p *DefaultAccessPolicy) Sanitize(actor *auth.ActorContext, role string, records []types.ActivityRecord) []types.ActivityRecord

Sanitize applies masking rules and IP redaction to activity records.

type EnrichedSink added in v0.14.0

type EnrichedSink struct {
	Sink         types.ActivitySink
	Enricher     ActivityEnricher
	ErrorHandler EnrichmentErrorHandler
}

EnrichedSink enriches activity records before logging them to a sink.

func (*EnrichedSink) Log added in v0.14.0

func (s *EnrichedSink) Log(ctx context.Context, record types.ActivityRecord) error

Log enriches the record (if configured) and forwards it to the sink.

type EnricherChain added in v0.14.0

type EnricherChain []ActivityEnricher

EnricherChain composes multiple enrichers in order.

func (EnricherChain) Enrich added in v0.14.0

Enrich applies the chain sequentially and stops on the first error.

func (EnricherChain) EnrichWithHandler added in v0.14.0

func (c EnricherChain) EnrichWithHandler(ctx context.Context, record types.ActivityRecord, handler EnrichmentErrorHandler) (types.ActivityRecord, error)

EnrichWithHandler applies the chain and delegates error handling when provided.

type EnricherFunc added in v0.14.0

type EnricherFunc func(ctx context.Context, record types.ActivityRecord) (types.ActivityRecord, error)

EnricherFunc adapts a function into an ActivityEnricher.

func (EnricherFunc) Enrich added in v0.14.0

Enrich executes the function and satisfies ActivityEnricher.

type EnricherWithHandler added in v0.14.0

type EnricherWithHandler interface {
	EnrichWithHandler(ctx context.Context, record types.ActivityRecord, handler EnrichmentErrorHandler) (types.ActivityRecord, error)
}

EnricherWithHandler allows enrichment with an explicit error handler.

type EnrichmentErrorHandler added in v0.14.0

type EnrichmentErrorHandler func(ctx context.Context, err error, enricher ActivityEnricher, current types.ActivityRecord, original types.ActivityRecord) (types.ActivityRecord, error)

EnrichmentErrorHandler decides how to handle errors during enrichment. Return a non-nil error to stop the chain. Return nil to continue using the returned record. Best-effort handlers should return the last successful record to allow partial enrichment.

func DefaultEnrichmentErrorHandler added in v0.14.0

func DefaultEnrichmentErrorHandler(strategy EnrichmentErrorStrategy) EnrichmentErrorHandler

DefaultEnrichmentErrorHandler returns a handler for the chosen strategy.

type EnrichmentErrorStrategy added in v0.14.0

type EnrichmentErrorStrategy int

EnrichmentErrorStrategy chooses how enrichment errors are handled.

const (
	// EnrichmentFailFast stops on the first error and returns the original record.
	EnrichmentFailFast EnrichmentErrorStrategy = iota
	// EnrichmentBestEffort keeps the last successful record and continues the chain.
	EnrichmentBestEffort
)

type EnrichmentScope added in v0.14.0

type EnrichmentScope string

EnrichmentScope determines when enrichment should apply.

const (
	EnrichmentScopeWrite    EnrichmentScope = "write"
	EnrichmentScopeBackfill EnrichmentScope = "backfill"
	EnrichmentScopeBoth     EnrichmentScope = "both"
)

type EnrichmentWriteMode added in v0.14.0

type EnrichmentWriteMode string

EnrichmentWriteMode determines how write-time enrichment is applied.

const (
	EnrichmentWriteModeNone    EnrichmentWriteMode = "none"
	EnrichmentWriteModeWrapper EnrichmentWriteMode = "wrapper"
	EnrichmentWriteModeRepo    EnrichmentWriteMode = "repo"
	EnrichmentWriteModeHybrid  EnrichmentWriteMode = "hybrid"
)

type EnvelopeMapper added in v0.17.0

type EnvelopeMapper struct {
	DefaultChannel    string
	DefaultObjectType string
}

EnvelopeMapper maps ActivityEnvelopeCarrier events to ActivityRecord.

func (EnvelopeMapper) Map added in v0.17.0

Map converts a generic envelope event into ActivityRecord.

type FilterConfig added in v0.10.0

type FilterConfig struct {
	ChannelAllowlist []string
	ChannelDenylist  []string

	// Machine activity settings are reserved for policy layers that can inspect
	// records (actor type/data); BuildFilterFromActor does not use them directly.
	MachineActivityEnabled bool
	MachineActorTypes      []string
	MachineDataKeys        []string

	SuperadminScope bool

	AdminRoleAliases      []string
	SuperadminRoleAliases []string
}

FilterConfig controls how BuildFilterFromActor applies role and channel rules.

type FilterOption added in v0.10.0

type FilterOption func(*FilterConfig)

FilterOption mutates the filter configuration.

func WithChannelAllowlist added in v0.10.0

func WithChannelAllowlist(channels ...string) FilterOption

WithChannelAllowlist restricts results to the provided channels.

func WithChannelDenylist added in v0.10.0

func WithChannelDenylist(channels ...string) FilterOption

WithChannelDenylist excludes the provided channels.

func WithMachineActivityEnabled added in v0.10.0

func WithMachineActivityEnabled(enabled bool) FilterOption

WithMachineActivityEnabled toggles machine activity visibility.

func WithMachineActorTypes added in v0.10.0

func WithMachineActorTypes(types ...string) FilterOption

WithMachineActorTypes overrides the machine actor type identifiers.

func WithMachineDataKeys added in v0.10.0

func WithMachineDataKeys(keys ...string) FilterOption

WithMachineDataKeys overrides the data keys used to flag machine activity.

func WithRoleAliases added in v0.10.0

func WithRoleAliases(adminAliases, superadminAliases []string) FilterOption

WithRoleAliases overrides the admin/superadmin role alias lists.

func WithSuperadminScope added in v0.10.0

func WithSuperadminScope(enabled bool) FilterOption

WithSuperadminScope allows superadmins to widen scope beyond actor context.

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 MetadataExposureStrategy added in v0.15.0

type MetadataExposureStrategy int

MetadataExposureStrategy controls how activity metadata is exposed on read.

const (
	// MetadataExposeNone returns no metadata.
	MetadataExposeNone MetadataExposureStrategy = iota
	// MetadataExposeSanitized returns metadata after sanitization.
	MetadataExposeSanitized
	// MetadataExposeAll returns raw metadata (intended for development/debug).
	MetadataExposeAll
)

type MetadataSanitizer added in v0.15.0

type MetadataSanitizer func(actor *auth.ActorContext, role string, record types.ActivityRecord) map[string]any

MetadataSanitizer customizes sanitized metadata exposure.

type ObjectInfo added in v0.14.0

type ObjectInfo struct {
	ID      string
	Type    string
	Display string
	Deleted bool
}

ObjectInfo defines enrichment details for an object.

type ObjectResolver added in v0.14.0

type ObjectResolver interface {
	ResolveObjects(ctx context.Context, objectType string, ids []string, meta ResolveContext) (map[string]ObjectInfo, error)
}

ObjectResolver resolves object enrichment details in batch.

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 RegistryMapper added in v0.17.0

type RegistryMapper struct {
	Registry *ActivityMapperRegistry
	Name     string
}

RegistryMapper resolves and delegates mapping to a named registry mapper.

func (*RegistryMapper) Map added in v0.17.0

Map resolves the named mapper and delegates event mapping.

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) ListActivityForEnrichment added in v0.14.0

func (r *Repository) ListActivityForEnrichment(ctx context.Context, filter ActivityEnrichmentFilter) (ActivityEnrichmentPage, error)

ListActivityForEnrichment returns activity records missing enrichment keys or stale by cutoff.

func (*Repository) Log

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

Log persists an activity record into the database.

func (*Repository) UpdateActivityData added in v0.14.0

func (r *Repository) UpdateActivityData(ctx context.Context, id uuid.UUID, data map[string]any) error

UpdateActivityData merges missing keys into the activity data payload.

func (*Repository) UpdateActivityDataWithOptions added in v0.14.0

func (r *Repository) UpdateActivityDataWithOptions(ctx context.Context, id uuid.UUID, data map[string]any, opts ActivityEnrichmentUpdateOptions) error

UpdateActivityDataWithOptions merges missing keys and optionally forces updates for specific keys.

type RepositoryConfig

type RepositoryConfig struct {
	DB         *bun.DB
	Repository repository.Repository[*LogEntry]
	Clock      types.Clock
	IDGen      types.IDGenerator
	Enricher   ActivityEnricher
	// EnrichmentErrorHandler controls fail-fast vs best-effort behavior.
	EnrichmentErrorHandler EnrichmentErrorHandler
}

RepositoryConfig wires the Bun-backed activity repository.

type ResolveContext added in v0.14.0

type ResolveContext struct {
	TenantID uuid.UUID
	ActorID  uuid.UUID
	Verb     string
	Source   string
	Metadata map[string]any
}

ResolveContext provides request-scoped data used by resolvers.

type SanitizerConfig added in v0.10.0

type SanitizerConfig struct {
	Masker *masker.Masker
}

SanitizerConfig controls the masker used for activity sanitization.

type SessionIDProvider added in v0.14.0

type SessionIDProvider interface {
	SessionID(ctx context.Context) (string, bool)
}

SessionIDProvider extracts a session identifier from the request context.

type SessionIDProviderFunc added in v0.14.0

type SessionIDProviderFunc func(ctx context.Context) (string, bool)

SessionIDProviderFunc adapts a function into a SessionIDProvider.

func (SessionIDProviderFunc) SessionID added in v0.14.0

func (f SessionIDProviderFunc) SessionID(ctx context.Context) (string, bool)

SessionID returns the session identifier and satisfies SessionIDProvider.

type UsersActivityHook added in v0.17.0

type UsersActivityHook struct {
	Sink            types.ActivitySink
	Mapper          ActivityEventMapper
	SessionProvider SessionIDProvider
	Enricher        ActivityEnricher
	ErrorHandler    EnrichmentErrorHandler
}

UsersActivityHook maps events into go-users activity records using a mapper.

func (*UsersActivityHook) Notify added in v0.17.0

func (h *UsersActivityHook) Notify(ctx context.Context, evt any) error

Notify maps and persists one event through the configured sink.

Jump to

Keyboard shortcuts

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