Documentation
¶
Overview ¶
Package activitylog is an Arandu module: the activity log of an application, on the pattern of spatie/laravel-activitylog v5, adapted to Arandu.
The files are laid out by role:
module.go -> registration, read routes, migration and scheduled clean config.go -> what the application passes in model.go -> the entry, its changes and its properties logger.go -> recording: the fluent chain, placeholders, context scopes record.go -> logging a record's changes, and the options that shape it values.go -> what a value becomes in the log policy.go -> who may read and clean service.go -> reading and cleaning, after the policy platform.go -> the read of every tenant, for the platform administrator commands.go -> what an operator runs from a terminal bridges.go -> the domain events and the mail an application already has
What is Spatie's, and what is Arandu's ¶
Everything Spatie v5 records, this records, in the same shape: a log name, a description with :subject, :causer and :properties placeholders, a subject and a causer, an event, the record's change as attribute_changes ({attributes, old}) and free properties. LogOptions are Spatie's: logAll, logOnly with relation and JSON paths, logExcept, logOnlyDirty, dontLogIfAttributesChangedOnly, dontLogEmptyChanges, useLogName, setDescriptionForEvent and useAttributeRawValues; a record may also choose its events, adjust each entry about it and turn its own logging off. The beforeLogging hooks, transformChanges, withoutLogging, defaultCauser, the causer resolver, the buffer and the clean are here too.
Three things differ, each because Arandu asks it:
- The tenant. Every entry carries the tenant of the Grant it was recorded under, data.Tenant(g), and every read is scoped by the Grant the policy issued. Nothing a caller names chooses whose log an entry lands in.
- The trigger. Spatie hooks Eloquent's events; a Hesape model event hands a callback the row and neither the context nor the Grant of the write, so a hook could not write in the same transaction and tenant without hidden state. A record's change is logged by saving it through the Logger -- logger.Save(ctx, g, record) in place of record.Save(ctx, g) -- which writes the row and its entry in one transaction.
- The scopes. Spatie's request-scoped switches and default causer are, in Go, values the context carries: WithoutLogging, WithCauser, Buffered.
Index ¶
- Constants
- Variables
- func Actions() []security.Action
- func Commands(deps Deps) ([]console.Command, error)
- func PlatformActions() []security.Action
- func PublishedPaths() []string
- func ViewNames() []string
- func ViewPackages() []string
- func WithCauser(ctx context.Context, ref Ref) context.Context
- func WithLogging(ctx context.Context) context.Context
- func WithoutLogging(ctx context.Context) context.Context
- type Activity
- func (a *Activity) Causer() Ref
- func (a *Activity) Changes() Changes
- func (row *Activity) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Activity, error)
- func (a *Activity) GetProperty(path string, fallback any) any
- func (a Activity) LogValue() slog.Value
- func (a *Activity) Properties() map[string]any
- func (row *Activity) Replicate(except ...string) (*Activity, error)
- func (a *Activity) SetChanges(changes Changes) error
- func (a *Activity) SetProperties(properties map[string]any) error
- func (a *Activity) SetProperty(key string, value any) error
- func (a *Activity) Subject() Ref
- type ActivityCollection
- func (c ActivityCollection) Contains(key any) bool
- func (c ActivityCollection) Except(keys ...any) ActivityCollection
- func (c ActivityCollection) Find(key any) *Activity
- func (c ActivityCollection) FindOrFail(key any) (*Activity, error)
- func (c ActivityCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (ActivityCollection, error)
- func (c ActivityCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error
- func (c ActivityCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error
- func (c ActivityCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error
- func (c ActivityCollection) ModelKeys() []any
- func (c ActivityCollection) Only(keys ...any) ActivityCollection
- func (c ActivityCollection) Pluck(column string) []any
- func (c ActivityCollection) Rows() model.Rows
- func (c ActivityCollection) ToArray() []map[string]any
- func (c ActivityCollection) Unique() ActivityCollection
- type ActivityPolicy
- type ActivityQuery
- func (q *ActivityQuery) AddSelect(columns ...any) *ActivityQuery
- func (q *ActivityQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *ActivityQuery) Base() *model.Builder
- func (q *ActivityQuery) Chunk(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *ActivityQuery) Clone() *ActivityQuery
- func (q *ActivityQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)
- func (q *ActivityQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Activity, error)
- func (q *ActivityQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Activity, error]
- func (q *ActivityQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, ...) (ActivityCollection, *pagination.CursorPage, error)
- func (q *ActivityQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *ActivityQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)
- func (q *ActivityQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)
- func (q *ActivityQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) Each(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *ActivityQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)
- func (q *ActivityQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Activity, error)
- func (q *ActivityQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (ActivityCollection, error)
- func (q *ActivityQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Activity, error)
- func (q *ActivityQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Activity, error)
- func (q *ActivityQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Activity, error)
- func (q *ActivityQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Activity, error)
- func (q *ActivityQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Activity, error)
- func (q *ActivityQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Activity, error)
- func (q *ActivityQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Activity, error)
- func (q *ActivityQuery) ForPage(page, perPage int) *ActivityQuery
- func (q *ActivityQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Activity, error)
- func (q *ActivityQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (ActivityCollection, error)
- func (q *ActivityQuery) GroupBy(groups ...any) *ActivityQuery
- func (q *ActivityQuery) Has(relation, operator string, count int, boolean string, ...) *ActivityQuery
- func (q *ActivityQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *ActivityQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *ActivityQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *ActivityQuery) Join(table any, first any, args ...any) *ActivityQuery
- func (q *ActivityQuery) Latest(column ...string) *ActivityQuery
- func (q *ActivityQuery) Limit(value int) *ActivityQuery
- func (q *ActivityQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *ActivityQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *ActivityQuery) New() (*Activity, error)
- func (q *ActivityQuery) Offset(value int) *ActivityQuery
- func (q *ActivityQuery) Oldest(column ...string) *ActivityQuery
- func (q *ActivityQuery) OrDoesntHave(relation string) *ActivityQuery
- func (q *ActivityQuery) OrHas(relation, operator string, count int) *ActivityQuery
- func (q *ActivityQuery) OrWhere(column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) OrWhereHas(relation string, callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) OrWhereNot(column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) OrWhereRelation(relation string, column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) OrderBy(column any, direction ...string) *ActivityQuery
- func (q *ActivityQuery) OrderByDesc(column any) *ActivityQuery
- func (q *ActivityQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (ActivityCollection, *pagination.LengthAwarePage, error)
- func (q *ActivityQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)
- func (q *ActivityQuery) Select(columns ...any) *ActivityQuery
- func (q *ActivityQuery) SelectRaw(expression string, bindings ...any) *ActivityQuery
- func (q *ActivityQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (ActivityCollection, *pagination.Page, error)
- func (q *ActivityQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Activity, error)
- func (q *ActivityQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *ActivityQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *ActivityQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)
- func (q *ActivityQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)
- func (q *ActivityQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Activity, error)
- func (q *ActivityQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, ...) (int64, error)
- func (q *ActivityQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *ActivityQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *ActivityQuery) Where(column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *ActivityQuery
- func (q *ActivityQuery) WhereBetween(column any, from, to any) *ActivityQuery
- func (q *ActivityQuery) WhereColumn(first any, args ...any) *ActivityQuery
- func (q *ActivityQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) WhereExists(callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) WhereHas(relation string, callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *ActivityQuery
- func (q *ActivityQuery) WhereIn(column any, values []any) *ActivityQuery
- func (q *ActivityQuery) WhereKey(id any) *ActivityQuery
- func (q *ActivityQuery) WhereNot(column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) WhereNotExists(callback func(*query.Builder)) *ActivityQuery
- func (q *ActivityQuery) WhereNotIn(column any, values []any) *ActivityQuery
- func (q *ActivityQuery) WhereNotNull(columns ...any) *ActivityQuery
- func (q *ActivityQuery) WhereNull(columns ...any) *ActivityQuery
- func (q *ActivityQuery) WhereRelation(relation string, column any, args ...any) *ActivityQuery
- func (q *ActivityQuery) With(relations ...string) *ActivityQuery
- func (q *ActivityQuery) WithAvg(relation, column string) *ActivityQuery
- func (q *ActivityQuery) WithCount(relations ...string) *ActivityQuery
- func (q *ActivityQuery) WithExists(relation string) *ActivityQuery
- func (q *ActivityQuery) WithMax(relation, column string) *ActivityQuery
- func (q *ActivityQuery) WithMin(relation, column string) *ActivityQuery
- func (q *ActivityQuery) WithSum(relation, column string) *ActivityQuery
- func (q *ActivityQuery) Without(relations ...string) *ActivityQuery
- func (q *ActivityQuery) WithoutGlobalScope(identifier string) *ActivityQuery
- func (q *ActivityQuery) WithoutGlobalScopes(identifiers ...string) *ActivityQuery
- type ActivityService
- func (s *ActivityService) CausedBy(ctx context.Context, actor security.Subject, record Record, q data.Query) ([]*Activity, error)
- func (s *ActivityService) Clean(ctx context.Context, actor security.Subject, days int, logName string) (int64, error)
- func (s *ActivityService) Count(ctx context.Context, actor security.Subject, filter Filter) (int64, error)
- func (s *ActivityService) Find(ctx context.Context, actor security.Subject, id string) (*Activity, error)
- func (s *ActivityService) ForSubject(ctx context.Context, actor security.Subject, record Record, q data.Query) ([]*Activity, error)
- func (s *ActivityService) List(ctx context.Context, actor security.Subject, filter Filter, q data.Query) ([]*Activity, error)
- func (s *ActivityService) Paginate(ctx context.Context, actor security.Subject, filter Filter, page, perPage int) (Page, error)
- func (s *ActivityService) ReadAll(ctx context.Context, actor security.Subject, q PlatformQuery) (PlatformPage, error)
- func (s *ActivityService) ReadEntry(ctx context.Context, actor security.Subject, id string) (PlatformEntry, error)
- func (s *ActivityService) ReadOwn(ctx context.Context, actor security.Subject, q OwnQuery) (PlatformPage, error)
- func (s *ActivityService) ReadOwnEntry(ctx context.Context, actor security.Subject, id string) (PlatformEntry, error)
- func (s *ActivityService) SimplePaginate(ctx context.Context, actor security.Subject, filter Filter, page, perPage int) (Page, error)
- type BeforeActivityLogged
- type Buffer
- type ChangeRow
- type Changes
- type Collection
- type Config
- type Deps
- type DoesNotRecordEvents
- type EventRecorder
- type Filter
- type ForceDeletable
- type IndexPageData
- type Labels
- type LogOptions
- func (o LogOptions) DontLogEmptyChanges() LogOptions
- func (o LogOptions) DontLogIfAttributesChangedOnly(attributes ...string) LogOptions
- func (o LogOptions) LogAll() LogOptions
- func (o LogOptions) LogEmptyChanges() LogOptions
- func (o LogOptions) LogExcept(attributes ...string) LogOptions
- func (o LogOptions) LogOnly(attributes ...string) LogOptions
- func (o LogOptions) LogOnlyDirty() LogOptions
- func (o LogOptions) SetDescriptionForEvent(fn func(event string) string) LogOptions
- func (o LogOptions) UseAttributeRawValues(attributes ...string) LogOptions
- func (o LogOptions) UseLogName(logName string) LogOptions
- type Logger
- func (l *Logger) Activity(ctx context.Context, g security.Grant) *PendingActivity
- func (l *Logger) BufferRequests(onError func(r *http.Request, err error)) func(http.Handler) http.Handler
- func (l *Logger) Buffered(ctx context.Context) (context.Context, *Buffer)
- func (l *Logger) Delete(ctx context.Context, g security.Grant, record Record) (bool, error)
- func (l *Logger) Enabled(ctx context.Context) bool
- func (l *Logger) ForceDelete(ctx context.Context, g security.Grant, record ForceDeletable) (bool, error)
- func (l *Logger) In(ctx context.Context, g security.Grant, logName string) *PendingActivity
- func (l *Logger) Restore(ctx context.Context, g security.Grant, record Restorable) (bool, error)
- func (l *Logger) Save(ctx context.Context, g security.Grant, record Record) (bool, error)
- func (l *Logger) WithBuffer(ctx context.Context, fn func(ctx context.Context) error) error
- type LoggingDisabled
- type LogsActivity
- type MailBody
- type MailRedactor
- type MailTransport
- type Module
- func (m *Module) Boot(context.Context) error
- func (m *Module) Logger() *Logger
- func (m *Module) Migrations() []foundation.Migration
- func (m *Module) Name() string
- func (m *Module) Publishes() []foundation.Publication
- func (m *Module) Routes(r *fhttp.Router)
- func (m *Module) Schedule() []foundation.Task
- func (m *Module) Service() *ActivityService
- type OwnQuery
- type Page
- type PendingActivity
- func (p *PendingActivity) By(record Record) *PendingActivity
- func (p *PendingActivity) ByAnonymous() *PendingActivity
- func (p *PendingActivity) ByRef(ref Ref) *PendingActivity
- func (p *PendingActivity) CausedBy(record Record) *PendingActivity
- func (p *PendingActivity) CausedByAnonymous() *PendingActivity
- func (p *PendingActivity) CreatedAt(at time.Time) *PendingActivity
- func (p *PendingActivity) Entry() *Activity
- func (p *PendingActivity) Event(event string) *PendingActivity
- func (p *PendingActivity) InLog(logName string) *PendingActivity
- func (p *PendingActivity) Log(description string) (*Activity, error)
- func (p *PendingActivity) On(record Record) *PendingActivity
- func (p *PendingActivity) OnRef(ref Ref) *PendingActivity
- func (p *PendingActivity) PerformedOn(record Record) *PendingActivity
- func (p *PendingActivity) SetEvent(event string) *PendingActivity
- func (p *PendingActivity) Tap(fn func(a *Activity, event string)) *PendingActivity
- func (p *PendingActivity) Unless(condition bool, fn func(*PendingActivity) *PendingActivity, ...) *PendingActivity
- func (p *PendingActivity) UseLog(logName string) *PendingActivity
- func (p *PendingActivity) When(condition bool, fn func(*PendingActivity) *PendingActivity, ...) *PendingActivity
- func (p *PendingActivity) WithChanges(changes Changes) *PendingActivity
- func (p *PendingActivity) WithProperties(properties map[string]any) *PendingActivity
- func (p *PendingActivity) WithProperty(key string, value any) *PendingActivity
- type PermissionPolicy
- type PlatformEntry
- type PlatformPage
- type PlatformQuery
- type PropertyRow
- type Record
- type RecordsEvents
- type Ref
- type Resource
- type Restorable
- type Row
- type ShowPageData
- type Typed
Constants ¶
const ( // EventsLogName is where EventRecorder writes the domain events. EventsLogName = "events" // MailLogName is where MailTransport writes the mail. MailLogName = "mail" )
The logs the two bridges write to by default.
const ( // DefaultLogName is the log an entry belongs to when nothing names one. DefaultLogName = "default" // DefaultCleanAfterDays is how old an entry is when the clean removes it. DefaultCleanAfterDays = 365 // DefaultCauserType is the kind a Grant's subject is recorded as. DefaultCauserType = "user" // DefaultPrefix is where the read routes are mounted when Config leaves // Prefix empty. DefaultPrefix = "/activity-log" // DefaultPageSize is how many entries one page answers with. DefaultPageSize = 50 // MaxPageSize is the ceiling PageSize is refused above. MaxPageSize = 200 // DefaultCleanSpec is when the scheduled clean runs: every day at 03:10. DefaultCleanSpec = "10 3 * * *" )
The defaults for the optional settings. They are constants rather than literals inside Config.withDefaults, so the value a reader finds here is the value the package uses.
const ( EventCreated = "created" EventUpdated = "updated" EventDeleted = "deleted" EventRestored = "restored" )
The events a record's changes are logged under, as Spatie's ActivityEvent names them.
const ( // ActivityView is reading one entry, or paging through the log. ActivityView security.Action = "activity.view" // ActivityClean is removing old entries. ActivityClean security.Action = "activity.clean" // ActivityViewAll is reading the log of every tenant at once: the platform // administrator's, through ActivityService.ReadAll. It is asked of // Config.PlatformPolicy and of no other policy, and neither policy this // package ships allows it. ActivityViewAll security.Action = "activity.view_all" // ActivityViewOwn is a person reading their own entries, of every tenant: // what they caused, and what an operator did inside their account -- // ActivityService.ReadOwn. Nobody grants it: every signed-in subject // holds it over its own entries, and over nobody else's. ActivityViewOwn security.Action = "activity.view_own" )
The actions the policy answers about. Recording is not one of them: an entry is recorded under the Grant of the act it describes.
const ( ViewIndex = "modules.activitylog.index" ViewShow = "modules.activitylog.show" )
The names the screens are registered under once compiled.
const ActivityRecord security.Action = "activity.record"
ActivityRecord is the action of the Grants this package records under on its own: the events it is handed and the mail it watches leave. An entry an application records takes the Grant of the act it describes instead.
const ActivityTable = "activity_log"
ActivityTable is the table this package owns. It is a constant because it is written in the migration, in the model and in the tests, and three spellings of one table disagree on the day one of them is changed.
const CommandPrefix = "activitylog:"
CommandPrefix is what every command of this package is called under.
const DateFormat = "2006-01-02T15:04:05.000000Z"
DateFormat is how a time is written in an entry: Laravel's serializeDate.
const ImpersonationProperty = "impersonation"
ImpersonationProperty is the property an entry recorded while an operator viewed or acted in somebody's account carries: who the operator was, whose account it was, and whether they could write in it.
const PublishCommand = "aru vendor:publish --apply"
PublishCommand is what an application runs to take the screens over.
Variables ¶
var ( // ErrNotFound is returned when no entry matches, including when it exists // in another tenant. The two cases are deliberately indistinguishable. ErrNotFound = errors.New("activitylog: entry not found") // ErrNoGrant is returned when an entry is recorded with a Grant that names // no tenant: an entry belongs to somebody, and the Grant is where who comes // from. ErrNoGrant = errors.New("activitylog: an entry is recorded under a Grant that names its tenant") )
The errors this package answers with.
var DefaultEvents = []string{EventCreated, EventUpdated, EventDeleted, EventRestored}
DefaultEvents are the events a record logs when its options name none.
Functions ¶
func Actions ¶ added in v0.2.0
Actions are the actions this package's policy answers about, for an application to declare beside its own -- in arandu-permission's catalogue, so an organization grants reading or cleaning its log to whom it decides, as permission.Actions() is declared.
func PlatformActions ¶ added in v0.4.0
PlatformActions are the actions of the platform rather than of an organization: ActivityViewAll. They are declared apart from Actions so a catalogue an organization grants from never holds them; an application gives them to its platform administrators only, and its Config.PlatformPolicy decides.
func PublishedPaths ¶ added in v0.2.0
func PublishedPaths() []string
PublishedPaths are where the sources land in the application.
func ViewNames ¶ added in v0.2.0
func ViewNames() []string
ViewNames are the names the screens register once compiled.
func ViewPackages ¶ added in v0.2.0
func ViewPackages() []string
ViewPackages are the compiled packages an application imports so the screens are linked.
func WithCauser ¶
WithCauser returns a context whose entries are caused by ref unless a chain names another: Spatie's Activity::defaultCauser. The zero Ref makes them anonymous.
func WithLogging ¶
WithLogging undoes WithoutLogging for what the returned context reaches.
Types ¶
type Activity ¶
type Activity struct {
model.Model
// ID is a version 7 UUID, so the identifiers of entries written in one
// instant still sort in the order they were written.
ID string `db:"id"`
// TenantID is the customer the entry belongs to. It is written from the
// Grant the entry was recorded with, never from anything a caller names.
TenantID string `db:"tenant_id"`
// LogName is the log the entry belongs to; one table holds as many logs
// as an application keeps.
LogName *string `db:"log_name"`
// Description is what happened, in words, after its placeholders were
// replaced.
Description string `db:"description"`
// SubjectType and SubjectID name what the entry is about.
SubjectType *string `db:"subject_type"`
SubjectID *string `db:"subject_id"`
// Event is the kind of change, for an entry about a record's change.
Event *string `db:"event"`
// CauserType and CauserID name who caused it; both are null for an entry
// with no causer.
CauserType *string `db:"causer_type"`
CauserID *string `db:"causer_id"`
// OnBehalfOf is whose account the entry was made in when an operator
// viewed or acted there: the identifier of the Grant's subject, the
// account's owner, while CauserID names the operator. It is null on every
// other entry, and on the entries written before the column existed, which
// carry the same identifier only in the impersonation property.
OnBehalfOf *string `db:"on_behalf_of"`
// AttributeChanges is the JSON of a record's change: what its attributes
// became and what they were. See Changes.
AttributeChanges *string `db:"attribute_changes"`
// PropertiesJSON is the JSON of whatever the caller attached. See
// Properties.
PropertiesJSON *string `db:"properties"`
// CreatedAt is when it happened, in UTC.
CreatedAt time.Time `db:"created_at"`
// UpdatedAt is when the entry was last written, in UTC.
UpdatedAt time.Time `db:"updated_at"`
}
Activity is one entry of the log: something that happened, to what, by whom.
It embeds the model, so a row returned by a query carries its connection. The two JSON columns are kept as text, which every engine stores the same way, and are read and written through the methods below rather than by hand.
func (*Activity) Fresh ¶
Fresh reads the row again, as a new Activity; it shadows the untyped one the embedded model promotes.
func (*Activity) GetProperty ¶
GetProperty reads one property by its dot path -- "address.city" -- and answers fallback when the path leads nowhere.
func (Activity) LogValue ¶
LogValue keeps what identifies an entry, and leaves out what it carries: an entry's properties and changes are exactly the data somebody chose to keep out of the ordinary logs.
func (*Activity) Properties ¶
Properties returns what the caller attached to the entry.
func (*Activity) Replicate ¶
Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.
func (*Activity) SetChanges ¶
SetChanges replaces the record's change the entry carries. An empty change is stored as null.
func (*Activity) SetProperties ¶
SetProperties replaces what the entry carries. No property is stored as null.
func (*Activity) SetProperty ¶
SetProperty adds one property to what the entry carries.
type ActivityCollection ¶
type ActivityCollection []*Activity
ActivityCollection is rows of Activity, as a query returns them.
func ActivityCollectionOf ¶
func ActivityCollectionOf(rows model.Rows) ActivityCollection
ActivityCollectionOf converts rows of the table of Activity. A nil input is a nil collection.
func (ActivityCollection) Contains ¶
func (c ActivityCollection) Contains(key any) bool
func (ActivityCollection) Except ¶
func (c ActivityCollection) Except(keys ...any) ActivityCollection
func (ActivityCollection) Find ¶
func (c ActivityCollection) Find(key any) *Activity
func (ActivityCollection) FindOrFail ¶
func (c ActivityCollection) FindOrFail(key any) (*Activity, error)
func (ActivityCollection) Fresh ¶
func (c ActivityCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (ActivityCollection, error)
func (ActivityCollection) LoadMissing ¶
func (ActivityCollection) ModelKeys ¶
func (c ActivityCollection) ModelKeys() []any
func (ActivityCollection) Only ¶
func (c ActivityCollection) Only(keys ...any) ActivityCollection
func (ActivityCollection) Pluck ¶
func (c ActivityCollection) Pluck(column string) []any
func (ActivityCollection) Rows ¶
func (c ActivityCollection) Rows() model.Rows
Rows converts the collection back to the core's rows.
func (ActivityCollection) ToArray ¶
func (c ActivityCollection) ToArray() []map[string]any
func (ActivityCollection) Unique ¶
func (c ActivityCollection) Unique() ActivityCollection
type ActivityPolicy ¶
type ActivityPolicy struct{}
ActivityPolicy is the policy this package ships, and it denies everything.
An application opens the log by passing its own policy in Config.Policy, beside the rest of its authorization; until it does, nobody reads or cleans the log through this package, which is the state to start from.
type ActivityQuery ¶
type ActivityQuery struct {
// contains filtered or unexported fields
}
ActivityQuery is a query on the table of Activity.
The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Activity rows or a value; there is no way to run one without a Grant.
func Activities ¶
func Activities(db model.DB) *ActivityQuery
Activities starts a query on the table of Activity, through db.
Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.
func (*ActivityQuery) AddSelect ¶
func (q *ActivityQuery) AddSelect(columns ...any) *ActivityQuery
func (*ActivityQuery) Base ¶
func (q *ActivityQuery) Base() *model.Builder
Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.
func (*ActivityQuery) Clone ¶
func (q *ActivityQuery) Clone() *ActivityQuery
Clone returns an independent copy of the query.
func (*ActivityQuery) CursorPaginate ¶
func (q *ActivityQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (ActivityCollection, *pagination.CursorPage, error)
func (*ActivityQuery) DoesntExist ¶
func (*ActivityQuery) DoesntHave ¶
func (q *ActivityQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) FindMany ¶
func (q *ActivityQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (ActivityCollection, error)
func (*ActivityQuery) FindOrFail ¶
func (*ActivityQuery) FirstOrCreate ¶
func (*ActivityQuery) FirstOrFail ¶
func (*ActivityQuery) FirstOrNew ¶
func (*ActivityQuery) FirstWhere ¶
func (*ActivityQuery) ForPage ¶
func (q *ActivityQuery) ForPage(page, perPage int) *ActivityQuery
func (*ActivityQuery) ForceCreate ¶
func (*ActivityQuery) Get ¶
func (q *ActivityQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (ActivityCollection, error)
Get runs the query and returns the rows.
func (*ActivityQuery) GroupBy ¶
func (q *ActivityQuery) GroupBy(groups ...any) *ActivityQuery
func (*ActivityQuery) Has ¶
func (q *ActivityQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) InsertOrIgnore ¶
func (*ActivityQuery) Join ¶
func (q *ActivityQuery) Join(table any, first any, args ...any) *ActivityQuery
func (*ActivityQuery) Latest ¶
func (q *ActivityQuery) Latest(column ...string) *ActivityQuery
func (*ActivityQuery) Limit ¶
func (q *ActivityQuery) Limit(value int) *ActivityQuery
func (*ActivityQuery) New ¶
func (q *ActivityQuery) New() (*Activity, error)
New returns an empty Activity on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.
func (*ActivityQuery) Offset ¶
func (q *ActivityQuery) Offset(value int) *ActivityQuery
func (*ActivityQuery) Oldest ¶
func (q *ActivityQuery) Oldest(column ...string) *ActivityQuery
func (*ActivityQuery) OrDoesntHave ¶
func (q *ActivityQuery) OrDoesntHave(relation string) *ActivityQuery
func (*ActivityQuery) OrHas ¶
func (q *ActivityQuery) OrHas(relation, operator string, count int) *ActivityQuery
func (*ActivityQuery) OrWhere ¶
func (q *ActivityQuery) OrWhere(column any, args ...any) *ActivityQuery
OrWhere adds an or-where clause; a func(*ActivityQuery) in place of the column adds a group.
func (*ActivityQuery) OrWhereDoesntHave ¶
func (q *ActivityQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) OrWhereHas ¶
func (q *ActivityQuery) OrWhereHas(relation string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) OrWhereNot ¶
func (q *ActivityQuery) OrWhereNot(column any, args ...any) *ActivityQuery
OrWhereNot adds an or-joined negated where clause, or a negated group.
func (*ActivityQuery) OrWhereRelation ¶
func (q *ActivityQuery) OrWhereRelation(relation string, column any, args ...any) *ActivityQuery
func (*ActivityQuery) OrderBy ¶
func (q *ActivityQuery) OrderBy(column any, direction ...string) *ActivityQuery
func (*ActivityQuery) OrderByDesc ¶
func (q *ActivityQuery) OrderByDesc(column any) *ActivityQuery
func (*ActivityQuery) Paginate ¶
func (q *ActivityQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (ActivityCollection, *pagination.LengthAwarePage, error)
func (*ActivityQuery) Select ¶
func (q *ActivityQuery) Select(columns ...any) *ActivityQuery
func (*ActivityQuery) SelectRaw ¶
func (q *ActivityQuery) SelectRaw(expression string, bindings ...any) *ActivityQuery
func (*ActivityQuery) SimplePaginate ¶
func (q *ActivityQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (ActivityCollection, *pagination.Page, error)
func (*ActivityQuery) UpdateOrCreate ¶
func (*ActivityQuery) ValueOrFail ¶
func (*ActivityQuery) Where ¶
func (q *ActivityQuery) Where(column any, args ...any) *ActivityQuery
Where adds a where clause; a func(*ActivityQuery) in place of the column adds a group.
func (*ActivityQuery) WhereBelongsTo ¶
func (q *ActivityQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *ActivityQuery
func (*ActivityQuery) WhereBetween ¶
func (q *ActivityQuery) WhereBetween(column any, from, to any) *ActivityQuery
func (*ActivityQuery) WhereColumn ¶
func (q *ActivityQuery) WhereColumn(first any, args ...any) *ActivityQuery
func (*ActivityQuery) WhereDoesntHave ¶
func (q *ActivityQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) WhereExists ¶
func (q *ActivityQuery) WhereExists(callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) WhereHas ¶
func (q *ActivityQuery) WhereHas(relation string, callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) WhereHasCount ¶
func (q *ActivityQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *ActivityQuery
func (*ActivityQuery) WhereIn ¶
func (q *ActivityQuery) WhereIn(column any, values []any) *ActivityQuery
func (*ActivityQuery) WhereKey ¶
func (q *ActivityQuery) WhereKey(id any) *ActivityQuery
func (*ActivityQuery) WhereNot ¶
func (q *ActivityQuery) WhereNot(column any, args ...any) *ActivityQuery
WhereNot adds a negated where clause, or a negated group.
func (*ActivityQuery) WhereNotExists ¶
func (q *ActivityQuery) WhereNotExists(callback func(*query.Builder)) *ActivityQuery
func (*ActivityQuery) WhereNotIn ¶
func (q *ActivityQuery) WhereNotIn(column any, values []any) *ActivityQuery
func (*ActivityQuery) WhereNotNull ¶
func (q *ActivityQuery) WhereNotNull(columns ...any) *ActivityQuery
func (*ActivityQuery) WhereNull ¶
func (q *ActivityQuery) WhereNull(columns ...any) *ActivityQuery
func (*ActivityQuery) WhereRelation ¶
func (q *ActivityQuery) WhereRelation(relation string, column any, args ...any) *ActivityQuery
func (*ActivityQuery) With ¶
func (q *ActivityQuery) With(relations ...string) *ActivityQuery
func (*ActivityQuery) WithAvg ¶
func (q *ActivityQuery) WithAvg(relation, column string) *ActivityQuery
func (*ActivityQuery) WithCount ¶
func (q *ActivityQuery) WithCount(relations ...string) *ActivityQuery
func (*ActivityQuery) WithExists ¶
func (q *ActivityQuery) WithExists(relation string) *ActivityQuery
func (*ActivityQuery) WithMax ¶
func (q *ActivityQuery) WithMax(relation, column string) *ActivityQuery
func (*ActivityQuery) WithMin ¶
func (q *ActivityQuery) WithMin(relation, column string) *ActivityQuery
func (*ActivityQuery) WithSum ¶
func (q *ActivityQuery) WithSum(relation, column string) *ActivityQuery
func (*ActivityQuery) Without ¶
func (q *ActivityQuery) Without(relations ...string) *ActivityQuery
func (*ActivityQuery) WithoutGlobalScope ¶
func (q *ActivityQuery) WithoutGlobalScope(identifier string) *ActivityQuery
func (*ActivityQuery) WithoutGlobalScopes ¶
func (q *ActivityQuery) WithoutGlobalScopes(identifiers ...string) *ActivityQuery
type ActivityService ¶
type ActivityService struct {
// contains filtered or unexported fields
}
ActivityService reads and cleans the log, after its policy answers.
It is the half of the package that a person reaches -- a screen, a terminal -- and every method authorizes before the first statement. The Logger is the other half, reached by code that already holds the Grant of what it records.
func NewActivityService ¶
func NewActivityService(db *data.DB, cfg Config) (*ActivityService, error)
NewActivityService wires the service over the application's database handle and the configured policy, ActivityPolicy when the configuration names none.
func (*ActivityService) CausedBy ¶ added in v0.2.0
func (s *ActivityService) CausedBy(ctx context.Context, actor security.Subject, record Record, q data.Query) ([]*Activity, error)
CausedBy pages through the entries a record caused, newest first -- Spatie's activitiesAsCauser.
func (*ActivityService) Clean ¶
func (s *ActivityService) Clean(ctx context.Context, actor security.Subject, days int, logName string) (int64, error)
Clean removes the entries older than days -- the configured CleanAfterDays when days is zero -- from one log, or from every log when logName is empty: Spatie's activitylog:clean. It answers how many were removed.
func (*ActivityService) Count ¶
func (s *ActivityService) Count(ctx context.Context, actor security.Subject, filter Filter) (int64, error)
Count is how many entries a filter keeps.
func (*ActivityService) Find ¶
func (s *ActivityService) Find(ctx context.Context, actor security.Subject, id string) (*Activity, error)
Find reads one entry.
func (*ActivityService) ForSubject ¶ added in v0.2.0
func (s *ActivityService) ForSubject(ctx context.Context, actor security.Subject, record Record, q data.Query) ([]*Activity, error)
ForSubject pages through the entries about a record, newest first -- Spatie's activitiesAsSubject.
func (*ActivityService) List ¶
func (s *ActivityService) List(ctx context.Context, actor security.Subject, filter Filter, q data.Query) ([]*Activity, error)
List pages through the log, newest first: by when each entry happened, and by identifier within one instant. The cursor is the identifier of the last entry of the previous page.
func (*ActivityService) Paginate ¶
func (s *ActivityService) Paginate(ctx context.Context, actor security.Subject, filter Filter, page, perPage int) (Page, error)
Paginate reads one numbered page of the log, newest first: Eloquent's paginate, which Spatie's activity queries end in. A page past the last is the last.
func (*ActivityService) ReadAll ¶ added in v0.4.0
func (s *ActivityService) ReadAll(ctx context.Context, actor security.Subject, q PlatformQuery) (PlatformPage, error)
ReadAll pages through the log of every tenant, or of the tenants q names, newest first: the platform administrator's read, who reads every entry where a user reads only their own.
It asks Config.PlatformPolicy, and no other policy, for ActivityViewAll. Nil there refuses everybody, as the two policies this package ships do, so the read exists only once the application wrote the rule that opens it for its platform administrators. A subject inside somebody else's account is refused before any policy: an operator acts there as the account, and the account reads no other tenant.
It pages by a keyset on (created_at, id), the order List pages in, and counts nothing. The cursor is the identifier of the last entry of the previous page; a page that ends on an entry removed since then is the last.
func (*ActivityService) ReadEntry ¶ added in v0.4.1
func (s *ActivityService) ReadEntry(ctx context.Context, actor security.Subject, id string) (PlatformEntry, error)
ReadEntry is one entry of any tenant's log, by its identifier: the platform administrator's read of a single entry, as ReadAll is of a page. It asks Config.PlatformPolicy for ActivityViewAll, refuses a subject inside somebody else's account before any policy, and answers ErrNotFound when no tenant holds the entry. The answer is a PlatformEntry snapshot, which holds no connection.
func (*ActivityService) ReadOwn ¶ added in v0.4.2
func (s *ActivityService) ReadOwn(ctx context.Context, actor security.Subject, q OwnQuery) (PlatformPage, error)
ReadOwn pages through a person's own entries in every tenant, newest first: what the acting subject caused, and what an operator did inside its account. It is the read a person has of their history -- "a user reads only their own entries" -- beside the platform administrator's ReadAll.
Every signed-in subject may read its own entries and nobody else's: the read is narrowed to Filter.Involves naming the subject, as the Grant's subject, and q.Filter.Involves is replaced. An operator inside somebody's account reads the account's, which is what the account's owner sees. A guest is refused.
func (*ActivityService) ReadOwnEntry ¶ added in v0.4.2
func (s *ActivityService) ReadOwnEntry(ctx context.Context, actor security.Subject, id string) (PlatformEntry, error)
ReadOwnEntry is one of a person's own entries, of any tenant: ErrNotFound for an entry that does not exist and for one that is somebody else's alike, so the answer says nothing about entries the reader may not see.
func (*ActivityService) SimplePaginate ¶ added in v0.4.0
func (s *ActivityService) SimplePaginate(ctx context.Context, actor security.Subject, filter Filter, page, perPage int) (Page, error)
SimplePaginate reads one numbered page of the log, newest first, without counting the log: Eloquent's simplePaginate. It reads one entry past the page to say whether another follows, and leaves Pages and Total at zero.
Paginate counts every entry the filter keeps, on every page, and that count grows with the log; this one costs what the page costs. A page past the last is empty rather than the last, since nothing says where the last is. List, which pages by a cursor rather than an offset, costs the same on the thousandth page as on the first.
type BeforeActivityLogged ¶
BeforeActivityLogged is implemented by a record that adjusts every entry about it, manual or automatic, right before it is written.
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer holds the entries recorded under a context made by Buffered, and writes them together when flushed: Spatie's buffer, scoped to the context rather than to the request lifecycle.
func (*Buffer) Flush ¶
Flush writes every waiting entry in one transaction, each under the Grant it was recorded with, and empties the buffer. When the write fails, the entries are kept for the next flush, as Spatie's buffer keeps them. ctx should not be the buffered context: the writes are the flush's own.
type ChangeRow ¶ added in v0.2.0
type ChangeRow struct{ Attribute, Old, New string }
ChangeRow is one attribute of a record's change.
type Changes ¶
type Changes struct {
Attributes map[string]any `json:"attributes,omitempty"`
Old map[string]any `json:"old,omitempty"`
}
Changes is a record's change: Attributes is what its logged attributes became, and Old is what they were. A created or restored record carries only Attributes; a deleted one only Old.
type Collection ¶
type Collection struct {
// contains filtered or unexported fields
}
Collection is one page of entries, and the cursor of the next page.
func (Collection) With ¶
func (c Collection) With() map[string]any
With is the framework's envelope hook; a collection adds nothing to it.
type Config ¶
type Config struct {
// Tenant is the customer a visitor with no session is read as, on the
// read routes. It comes from the application's own configuration, never
// from the request.
Tenant string
// Disabled turns recording off. Nothing is written while it is true, and
// Log answers a nil entry, as Spatie's ACTIVITYLOG_ENABLED=false does.
Disabled bool
// DefaultLogName is the log an entry belongs to when nothing names one.
// Empty means DefaultLogName.
DefaultLogName string
// CleanAfterDays is how old an entry is when the clean removes it. Zero
// means DefaultCleanAfterDays.
CleanAfterDays int
// CleanSpec is the five-field cron expression the scheduled clean runs
// on. Empty means DefaultCleanSpec; "-" schedules no clean.
CleanSpec string
// CleanTenants are tenants the scheduled clean also reaches, beside the
// ones the scheduler lists for per-tenant work: the application's own
// tenant, where the mail log lives, is usually one.
CleanTenants []string
// DefaultExceptAttributes are never logged from any record, whatever its
// options say: the place for a password hash, a token, a secret.
DefaultExceptAttributes []string
// CauserType is the kind the Grant's subject is recorded as when an entry
// names no causer. Empty means DefaultCauserType.
CauserType string
// ResolveCauser decides who caused an entry that names no causer, in
// place of the Grant's subject -- Spatie's CauserResolver::resolveUsing.
// It may answer the zero Ref for an anonymous entry.
ResolveCauser func(ctx context.Context, g security.Grant) Ref
// BeforeLogging runs on every entry, in order, right before it is
// written -- Spatie's Activity::beforeLogging. It may change anything on
// the entry, such as adding the request's address to its properties. An
// error stops the entry and is answered to the caller.
BeforeLogging []func(ctx context.Context, a *Activity) error
// TransformChanges runs on every entry that carries a record's change,
// before BeforeLogging -- Spatie's LogActivityAction::transformChanges,
// the place to redact a field.
TransformChanges func(a *Activity)
// Policy decides who may read and clean the log. Nil is ActivityPolicy,
// which denies everything: a wiring that says nothing about rules gets no
// access at all, which is the state to start from. Recording needs no
// policy: it takes the Grant the caller's own action was authorized with.
Policy security.Policy[Activity]
// PlatformPolicy decides who reads the log of every tenant at once --
// ActivityService.ReadAll, under ActivityViewAll: the platform
// administrator, who reads every entry where a user reads only their own.
// It is the only policy ReadAll asks, so a Policy that opens the log of an
// organization, however widely, opens nothing across organizations. Nil
// refuses everybody, and so do ActivityPolicy and PermissionPolicy: the
// application writes this one for its platform administrators.
PlatformPolicy security.Policy[Activity]
// Prefix is the path the read routes are mounted under. Empty means
// DefaultPrefix.
Prefix string
// Screens draws the read routes as pages -- the log and one entry -- for
// a request that does not ask for JSON. The application publishes and
// compiles the views first (see PublishCommand); Boot refuses to start
// without them.
Screens bool
// Locale is the language of the screens: "en" or "pt-BR". Empty is "en".
Locale string
// Name names a subject or a causer on the screens -- a user by their name
// rather than their identifier. Nil, or an empty answer, shows the kind and
// a short identifier.
Name func(ctx context.Context, ref Ref) string
// PageSize is how many entries one page answers with. Zero means
// DefaultPageSize, and anything above MaxPageSize is refused.
PageSize int
// SimplePagination pages the screens with SimplePaginate: Previous and
// Next, and no page count or total. Paginate counts every entry of the
// tenant's log the filter keeps on every page drawn, and that count grows
// with the log; this costs what the page costs. False keeps the numbered
// pages. The JSON route pages by cursor either way and counts nothing.
//
// A published index view older than this option prints "of 0 · 0" under
// it; publishing the views again draws the page without the count.
SimplePagination bool
}
Config is what the application passes when it wires this package: Spatie's config/activitylog.php, as a typed struct. A misspelled key in a map is a setting that silently keeps its default; a field that does not exist does not compile.
type Deps ¶
type Deps struct {
// Service is the same service the routes call, so a clean from a terminal
// passes the same policy as one from anywhere else.
Service *ActivityService
// Operator says who a command runs as, for the customer it names. A
// command has no session; who it runs as is the application's answer.
Operator func(tenant string) security.Subject
}
Deps is what the commands need, built by the application where it wires everything else.
type DoesNotRecordEvents ¶ added in v0.2.0
type DoesNotRecordEvents interface {
ActivityEventsNotRecorded() []string
}
DoesNotRecordEvents is implemented by a record that leaves some events out of the ones it records -- Spatie's static $doNotRecordEvents.
type EventRecorder ¶
type EventRecorder struct {
// LogName decides the log of an event; nil writes every event to
// EventsLogName.
LogName func(e events.Stored) string
// Describe decides the description of an event; nil uses its name.
Describe func(e events.Stored) string
// Payload keeps the event's payload in the entry's properties, under
// "payload". It is off by default: a payload is whatever the producer
// stored, and the log is read by more people than the outbox is.
Payload bool
// contains filtered or unexported fields
}
EventRecorder writes every committed domain event of the outbox into the activity log, so everything an application already records as an event -- in its own transaction, with the tenant, the subject whose Grant authorized it and the action -- is also in the log, read by Spatie's scopes.
It is an events.Publisher: the relay hands it each event once the write that produced it committed. The relay delivers at least once, so the entry takes the event's identifier and a second delivery finds it written and does nothing.
func NewEventRecorder ¶
func NewEventRecorder(logger *Logger) *EventRecorder
NewEventRecorder returns the recorder over the logger.
type Filter ¶
type Filter struct {
// LogNames keeps the entries of these logs.
LogNames []string
// Event keeps the entries of this event.
Event string
// Subject keeps the entries about this thing; a Ref with only a Type
// keeps every entry about that kind.
Subject Ref
// Causer keeps the entries caused by this one; a Ref with only a Type
// keeps every entry caused by that kind.
Causer Ref
// Involves keeps the entries this one caused and the entries an operator
// made inside its account -- causer_id, or on_behalf_of, is its ID: what a
// person reads as "what was done by me or for me". Its Type narrows the
// causer as Causer's does; on_behalf_of names an account of the kind
// Config.CauserType records, so a Type of another kind matches only as
// causer. A Ref with no ID narrows nothing.
//
// The column on_behalf_of is not backfilled: an entry made inside an
// account before the column was added is found as the operator's, by
// Causer, and not as the account's.
Involves Ref
// Since and Until bound when the entries happened, Since inclusive and
// Until exclusive.
Since, Until time.Time
// Search keeps the entries whose description contains this.
Search string
}
Filter narrows a listing: Spatie's inLog, causedBy, forSubject and forEvent scopes, and a window of time. Every field left empty narrows nothing.
type ForceDeletable ¶ added in v0.2.0
type ForceDeletable interface {
Record
ForceDelete(ctx context.Context, g security.Grant) (bool, error)
}
ForceDeletable is a record of a table with soft deletes that can be removed for good.
type IndexPageData ¶ added in v0.2.0
type IndexPageData struct {
view.Page
Prefix, Log, Event, Search string
Labels Labels
Rows []Row
PageNumber, Pages, Total int
PreviousURL, NextURL string
// Counted says Pages and Total were counted. Under
// Config.SimplePagination they were not, and the page draws only its
// number.
Counted bool
}
IndexPageData is the log, one page of it.
type Labels ¶ added in v0.2.0
type Labels struct {
// contains filtered or unexported fields
}
Labels are the words of the screens, in one locale.
type LogOptions ¶
type LogOptions struct {
// contains filtered or unexported fields
}
LogOptions say which of a record's attributes are logged and when: Spatie's LogOptions, as a value built by chaining.
func DefaultLogOptions ¶
func DefaultLogOptions() LogOptions
DefaultLogOptions logs the events with no attribute, and logs an entry even when its change is empty.
func (LogOptions) DontLogEmptyChanges ¶
func (o LogOptions) DontLogEmptyChanges() LogOptions
DontLogEmptyChanges skips an entry whose change carries nothing.
func (LogOptions) DontLogIfAttributesChangedOnly ¶
func (o LogOptions) DontLogIfAttributesChangedOnly(attributes ...string) LogOptions
DontLogIfAttributesChangedOnly skips an update that changed nothing but the named attributes -- typically updated_at.
func (LogOptions) LogEmptyChanges ¶
func (o LogOptions) LogEmptyChanges() LogOptions
LogEmptyChanges logs an entry even when its change carries nothing.
func (LogOptions) LogExcept ¶
func (o LogOptions) LogExcept(attributes ...string) LogOptions
LogExcept never logs the named attributes.
func (LogOptions) LogOnly ¶
func (o LogOptions) LogOnly(attributes ...string) LogOptions
LogOnly logs the named attributes, in place of any named before, as Spatie's logOnly replaces. "a.b" reads b on the relation a and is kept under the flat key "a.b"; "a->b" reads b inside the JSON column a and is kept nested.
func (LogOptions) LogOnlyDirty ¶
func (o LogOptions) LogOnlyDirty() LogOptions
LogOnlyDirty keeps, in an update, only the attributes whose value changed.
func (LogOptions) SetDescriptionForEvent ¶
func (o LogOptions) SetDescriptionForEvent(fn func(event string) string) LogOptions
SetDescriptionForEvent describes the record's entries; the default is the event's name. An empty description skips the entry.
func (LogOptions) UseAttributeRawValues ¶
func (o LogOptions) UseAttributeRawValues(attributes ...string) LogOptions
UseAttributeRawValues keeps the named attributes as the record holds them, without the normalization every other value gets.
func (LogOptions) UseLogName ¶
func (o LogOptions) UseLogName(logName string) LogOptions
UseLogName puts the record's entries in the named log.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger records entries: Spatie's ActivityLogger, reached by a value the application holds rather than by a global helper.
An entry is recorded under the Grant the caller's own action was authorized with. That is the whole of its authorization, and it is enough: the entry is a side effect of an act the policy already allowed, it lands in that act's tenant -- data.Tenant(g), never a tenant somebody names -- and in that act's transaction when the context carries one. Reading and cleaning the log are separate acts with their own policy; see ActivityService.
func (*Logger) Activity ¶
Activity starts an entry in the default log, recorded under g: Spatie's activity(). Nothing is written until Log.
func (*Logger) BufferRequests ¶ added in v0.2.0
func (l *Logger) BufferRequests(onError func(r *http.Request, err error)) func(http.Handler) http.Handler
BufferRequests is a middleware that buffers what each request records and flushes it once the response is written -- Spatie's flush on terminate. A failed flush is reported to onError, which may be nil.
func (*Logger) Buffered ¶
Buffered returns a context whose entries are kept rather than written, and the buffer that keeps them. Flush writes them; an entry kept and never flushed is never written.
func (*Logger) Delete ¶
Delete deletes the record under g and logs it, with what its logged attributes were, in one transaction. On a table with soft deletes the row stays, stamped, and the entry is the same.
func (*Logger) ForceDelete ¶ added in v0.2.0
func (l *Logger) ForceDelete(ctx context.Context, g security.Grant, record ForceDeletable) (bool, error)
ForceDelete removes the record for good under g and logs it as deleted, as Spatie logs a forceDelete.
func (*Logger) In ¶
In starts an entry in the named log: Spatie's activity('name'). An empty name is the default log, as a falsy name is in Spatie.
func (*Logger) Restore ¶
Restore restores a soft-deleted record under g and logs it, in one transaction.
func (*Logger) Save ¶
Save saves the record under g and logs its creation or its update, in one transaction: the row and the entry about it are written together or not at all. It answers what the record's own Save answers.
What the attributes were is read from the row as stored before the write, and what they became is read back after it, both with the relations the options name -- Spatie's getRawOriginal and fresh(). Clearing deleted_at is a restore, and is not logged as an update, as Spatie's isRestoring.
func (*Logger) WithBuffer ¶ added in v0.2.0
WithBuffer runs fn under a buffered context and flushes what it recorded when fn returns, whether or not it failed -- Spatie's flush after a job, processed or failed. The flush's error is answered when fn's is nil.
type LoggingDisabled ¶
type LoggingDisabled interface {
ActivityLoggingDisabled() bool
}
LoggingDisabled is implemented by a record that can turn its own logging off -- Spatie's $model->disableLogging().
type LogsActivity ¶
type LogsActivity interface {
ActivityLogOptions() LogOptions
}
LogsActivity is implemented by a record that says how its changes are logged -- Spatie's getActivitylogOptions. A record that does not implement it logs its events with no attribute.
type MailBody ¶ added in v0.6.0
type MailBody struct {
HTML, Text string
}
MailBody is what of a rendered message a MailTransport keeps in its entry: the HTML and the text, as a MailRedactor returned them.
type MailRedactor ¶ added in v0.6.0
MailRedactor decides what of a rendered message the mail log may keep: the body with every secret it carries -- a one-time code, a sign-in link, a token -- replaced, and whether to keep it at all. It is the application's, because only the application knows which of its templates carries a secret and in which field; the log is read by more people than the mailbox is.
type MailTransport ¶
type MailTransport struct {
// contains filtered or unexported fields
}
MailTransport is a mail transport that writes every message it is handed into the activity log of one tenant -- the mail log -- and hands it on. A message the next transport refused is logged as failed, with the reason, and the refusal is answered to the sender as it would have been.
func NewMailTransport ¶
NewMailTransport watches next, writing to the log of tenant -- the application's own, from its configuration, never from a message.
func (*MailTransport) Send ¶
Send hands the message on, then writes the entry. A failure to write the entry is not the message's failure: the message is already gone.
func (*MailTransport) Unwrap ¶
func (t *MailTransport) Unwrap() mail.Transport
Unwrap is the watched transport.
func (*MailTransport) WithBody ¶ added in v0.6.0
func (t *MailTransport) WithBody(redact MailRedactor) *MailTransport
WithBody makes the transport keep each message's rendered body, as redact returns it, under the entry's properties "html" and "text", beside "body_redacted": true. A message redact declines keeps the envelope alone, and so does every message of a transport without one, as before.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is what the application registers.
func New ¶
New returns the module, or the reason it cannot be built. sessions is where the read routes take their subject from; it may be nil for an application that mounts no route of this package.
func (*Module) Boot ¶ added in v0.2.0
Boot refuses to start an application that asked for the screens and did not publish and compile them: one refusal at start-up that names the views and the command, rather than an error on the first page somebody opened.
func (*Module) Logger ¶
Logger records entries. It is the one Logger of the module, so an entry recorded by code and one read through a route are the same rows.
func (*Module) Migrations ¶
func (m *Module) Migrations() []foundation.Migration
Migrations declares the schema this module owns.
func (*Module) Publishes ¶ added in v0.2.0
func (m *Module) Publishes() []foundation.Publication
Publishes declares the screens' sources for `aru vendor:publish`.
func (*Module) Routes ¶
Routes registers the read routes under the configured prefix: the log, one entry. They answer JSON, and the policy decides who may read.
func (*Module) Schedule ¶
func (m *Module) Schedule() []foundation.Task
Schedule declares the daily clean, per tenant, under the clean action: Spatie's scheduled activitylog:clean. CleanSpec "-" declares none. The tenants in CleanTenants are cleaned by a second task, once each.
func (*Module) Service ¶
func (m *Module) Service() *ActivityService
Service reads and cleans the log.
type OwnQuery ¶ added in v0.4.2
type OwnQuery struct {
Filter
// Cursor is NextCursor of the page before; empty is the first page.
Cursor string
// Limit is how many entries a page holds; zero is the default.
Limit int
}
OwnQuery is what ReadOwn reads: the narrowing every listing takes and the page. Filter.Involves is the reader's, whatever it says.
type Page ¶
type Page struct {
Items []*Activity
Page, Pages, Total int
// HasMore says a page follows this one.
HasMore bool
}
Page is one numbered page of the log and where it sits among the rest -- what a screen with page numbers draws.
Paginate fills every field. SimplePaginate counts nothing, so it leaves Pages and Total at zero and says only whether a page follows.
type PendingActivity ¶
type PendingActivity struct {
// contains filtered or unexported fields
}
PendingActivity is an entry being described: Spatie's PendingActivityLog. Every method writes into the entry at once and returns the chain, so a tap sees what the chain set before it, and a method after a tap overrides it, as in Spatie. Log writes it and starts a fresh one.
It is not safe for concurrent use; it is meant to live for one chain.
func (*PendingActivity) By ¶
func (p *PendingActivity) By(record Record) *PendingActivity
By says who caused the entry: a record, whose kind and key become the causer and whose attributes the :causer placeholders read. A nil record changes nothing, as causedBy(null) does in Spatie. CausedBy is the same method under Spatie's other name.
func (*PendingActivity) ByAnonymous ¶
func (p *PendingActivity) ByAnonymous() *PendingActivity
ByAnonymous records the entry with no causer, whoever holds the Grant. CausedByAnonymous is the same method under Spatie's other name.
func (*PendingActivity) ByRef ¶
func (p *PendingActivity) ByRef(ref Ref) *PendingActivity
ByRef says who caused the entry by reference.
func (*PendingActivity) CausedBy ¶
func (p *PendingActivity) CausedBy(record Record) *PendingActivity
CausedBy is By.
func (*PendingActivity) CausedByAnonymous ¶
func (p *PendingActivity) CausedByAnonymous() *PendingActivity
CausedByAnonymous is ByAnonymous.
func (*PendingActivity) CreatedAt ¶
func (p *PendingActivity) CreatedAt(at time.Time) *PendingActivity
CreatedAt dates the entry; the default is now.
func (*PendingActivity) Entry ¶ added in v0.2.0
func (p *PendingActivity) Entry() *Activity
Entry is the entry being described, for what the chain has no method for.
func (*PendingActivity) Event ¶
func (p *PendingActivity) Event(event string) *PendingActivity
Event names the kind of change the entry records. SetEvent is the same method under Spatie's other name.
func (*PendingActivity) InLog ¶
func (p *PendingActivity) InLog(logName string) *PendingActivity
InLog puts the entry in the named log; an empty name leaves the entry with no log, as Spatie's useLog(null). UseLog is the same method under Spatie's other name.
func (*PendingActivity) Log ¶
func (p *PendingActivity) Log(description string) (*Activity, error)
Log writes the entry and answers it. Its description is one a tap already set or else description, with its placeholders replaced. While logging is off -- by configuration or by WithoutLogging -- nothing is written, the answer is nil with no error, and the entry being described is kept, as in Spatie.
Inside a context made by Buffered the entry is kept and written when the buffer is flushed; its identifier is already set.
func (*PendingActivity) On ¶
func (p *PendingActivity) On(record Record) *PendingActivity
On says what the entry is about: a record, whose kind and key become the subject and whose attributes the :subject placeholders read. PerformedOn is the same method under Spatie's other name.
func (*PendingActivity) OnRef ¶
func (p *PendingActivity) OnRef(ref Ref) *PendingActivity
OnRef says what the entry is about by reference, for a thing that is not a row of this database.
func (*PendingActivity) PerformedOn ¶
func (p *PendingActivity) PerformedOn(record Record) *PendingActivity
PerformedOn is On.
func (*PendingActivity) SetEvent ¶ added in v0.2.0
func (p *PendingActivity) SetEvent(event string) *PendingActivity
SetEvent is Event.
func (*PendingActivity) Tap ¶
func (p *PendingActivity) Tap(fn func(a *Activity, event string)) *PendingActivity
Tap runs fn on the entry now, with the chain's event -- the place to set anything the chain has no method for. As in Spatie, a method called after the tap overrides what the tap set.
func (*PendingActivity) Unless ¶
func (p *PendingActivity) Unless(condition bool, fn func(*PendingActivity) *PendingActivity, otherwise ...func(*PendingActivity) *PendingActivity) *PendingActivity
Unless applies fn to the chain when condition does not hold, and otherwise the optional default.
func (*PendingActivity) UseLog ¶
func (p *PendingActivity) UseLog(logName string) *PendingActivity
UseLog is InLog.
func (*PendingActivity) When ¶
func (p *PendingActivity) When(condition bool, fn func(*PendingActivity) *PendingActivity, otherwise ...func(*PendingActivity) *PendingActivity) *PendingActivity
When applies fn to the chain when condition holds, and otherwise the optional default -- Spatie's Conditionable.
func (*PendingActivity) WithChanges ¶
func (p *PendingActivity) WithChanges(changes Changes) *PendingActivity
WithChanges sets the record's change the entry carries.
func (*PendingActivity) WithProperties ¶
func (p *PendingActivity) WithProperties(properties map[string]any) *PendingActivity
WithProperties replaces what the entry carries.
func (*PendingActivity) WithProperty ¶
func (p *PendingActivity) WithProperty(key string, value any) *PendingActivity
WithProperty adds one property to what the entry carries.
type PermissionPolicy ¶ added in v0.2.0
type PermissionPolicy struct{}
PermissionPolicy decides by the subject's own actions, which arandu-permission fills: it allows an action the subject was granted, on an entry of the subject's tenant. It is the policy for an application that administers its permissions there.
func (PermissionPolicy) Can ¶ added in v0.2.0
func (PermissionPolicy) Can(_ context.Context, s security.Subject, a security.Action, record Activity) error
Can allows a granted action on an entry of the subject's tenant. It never allows ActivityViewAll: an action an organization's group could be given is not one that reads every organization, and the platform read is opened only by a policy the application writes for its platform administrators.
type PlatformEntry ¶ added in v0.4.0
type PlatformEntry struct {
// Tenant is the customer the entry belongs to -- what a platform screen
// shows beside it, and the one field no tenant's own listing answers.
Tenant string
ID string
LogName string
Description string
Event string
Subject Ref
Causer Ref
// OnBehalfOf is whose account an operator made the entry in, or empty.
OnBehalfOf string
Changes Changes
Properties map[string]any
CreatedAt time.Time
}
PlatformEntry is one entry of any tenant's log as ReadAll answers it.
It is a snapshot and not an Activity: an Activity carries the connection it was read with, and one read without the tenant filter could be saved or deleted through it in a tenant nobody authorized. A PlatformEntry holds no connection, so the platform read stays a read.
type PlatformPage ¶ added in v0.4.0
type PlatformPage struct {
Entries []PlatformEntry
NextCursor string
}
PlatformPage is one page of ReadAll, newest first, and the cursor of the next. NextCursor is empty on the last page.
type PlatformQuery ¶ added in v0.4.0
type PlatformQuery struct {
Filter
// Tenants keeps the entries of these tenants. Empty keeps every tenant's.
Tenants []string
// Cursor is the NextCursor of the previous page; empty is the first.
Cursor string
// Limit is how many entries a page holds: zero means 50, and more than 200
// is 200.
Limit int
}
PlatformQuery is what ReadAll reads: the narrowing every listing takes, the tenants to keep, and the page.
type PropertyRow ¶ added in v0.2.0
type PropertyRow struct{ Key, Value string }
PropertyRow is one property of an entry.
type Record ¶
type Record interface {
model.Entity
Exists() bool
GetKey() any
Table() *model.Table
GetAttributes() map[string]any
GetOriginal() map[string]any
GetDirty() map[string]any
ToArray() map[string]any
Save(ctx context.Context, g security.Grant) (bool, error)
Delete(ctx context.Context, g security.Grant) (bool, error)
}
Record is a row of the application's that an entry can be about or caused by: any struct embedding the Hesape Model, whose methods it promotes.
type RecordsEvents ¶
type RecordsEvents interface {
ActivityEvents() []string
}
RecordsEvents is implemented by a record that logs other events than DefaultEvents -- Spatie's static $recordEvents.
type Ref ¶
Ref names one thing an entry is about or was caused by: its kind and its identifier within that kind. Neither is a Go type: this package never loads the row on the other side.
type Resource ¶
type Resource struct {
// contains filtered or unexported fields
}
Resource is the list of fields one entry answers with. The tenant is not one of them: it names a customer and belongs in no response.
func NewResource ¶
NewResource snapshots one entry for the response.
type Restorable ¶
Restorable is a record of a table with soft deletes.
type ShowPageData ¶ added in v0.2.0
type ShowPageData struct {
view.Page
Prefix string
Labels Labels
Row Row
Changes []ChangeRow
Properties []PropertyRow
}
ShowPageData is one entry.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
everytenant
Package everytenant is the activity table read without the tenant filter: the one read of arandu-activitylog that crosses tenants, ActivityService.ReadAll, after the application's platform policy allowed it.
|
Package everytenant is the activity table read without the tenant filter: the one read of arandu-activitylog that crosses tenants, ActivityService.ReadAll, after the application's platform policy allowed it. |