doc: add the audit-logger example for faq page (#913)

See #830
This commit is contained in:
Ariel Mashraki
2020-11-03 10:56:58 +02:00
committed by GitHub
parent dc672bf18b
commit 404e5b3f38

View File

@@ -6,18 +6,12 @@ sidebar_label: FAQ
## Questions
[How to create a struct (or a mutation) level validator?](#how-to-create-a-mutation-level-validator)
[How to create an entity from a struct `T`?](#how-to-create-an-entity-from-a-struct-t)
[How to create an entity from a struct `T`?](#how-to-create-an-entity-from-a-struct-t)
[How to create a struct (or a mutation) level validator?](#how-to-create-a-mutation-level-validator)
[How to write an audit-log extension?](#how-to-write-an-audit-log-extension)
## Answers
#### How to create a mutation level validator?
In order to implement a mutation-level validator, you can either use [schema hooks](hooks.md#schema-hooks) for validating
changes applied on one entity type, or use [transaction hooks](transactions.md#hooks) for validating mutations that being
applied on multiple entity types (e.g. a GraphQL mutation).
#### How to create an entity from a struct `T`?
The different builders don't support the option of setting the entity fields (or edges) from a given struct `T`.
@@ -41,4 +35,123 @@ use the following template:
return {{ $receiver }}
}
{{ end }}
```
#### How to create a mutation level validator?
In order to implement a mutation-level validator, you can either use [schema hooks](hooks.md#schema-hooks) for validating
changes applied on one entity type, or use [transaction hooks](transactions.md#hooks) for validating mutations that being
applied on multiple entity types (e.g. a GraphQL mutation). For example:
```go
// A VersionHook is a dummy example for a hook that validates the "version" field
// is incremented by 1 on each update. Note that this is just a dummy example, and
// it doesn't promise consistency in the database.
func VersionHook() ent.Hook {
type OldSetVersion interface {
SetVersion(int)
Version() (int, bool)
OldVersion(context.Context) (int, error)
}
return func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
ver, ok := m.(OldSetVersion)
if !ok {
return next.Mutate(ctx, m)
}
oldV, err := ver.OldVersion(ctx)
if err != nil {
return nil, err
}
curV, exists := ver.Version()
if !exists {
return nil, fmt.Errorf("version field is required in update mutation")
}
if curV != oldV+1 {
return nil, fmt.Errorf("version field must be incremented by 1")
}
// Add an SQL predicate that validates the "version" column is equal
// to "oldV" (ensure it wasn't changed during the mutation by others).
return next.Mutate(ctx, m)
})
}
}
```
#### How to write an audit-log extension?
The preferred way for writing such an extension is to use [ent.Mixin](schema-mixin.md). Use the `Fields` option for
setting the fields that are shared between all schemas that import the mixed-schema, and use the `Hooks` option for
attaching a mutation-hook for all mutations that are being applied on these schemas. Here's an example, based on a
discussion in the [repository issue-tracker](https://github.com/facebook/ent/issues/830):
```go
// AuditMixin implements the ent.Mixin for sharing
// audit-log capabilities with package schemas.
type AuditMixin struct{
mixin.Schema
}
// Fields of the AuditMixin.
func (AuditMixin) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Immutable().
Default(time.Now),
field.Int("created_by").
Optional(),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now),
field.Int("updated_by").
Optional(),
}
}
// Hooks of the AuditMixin.
func (AuditMixin) Hooks() []ent.Hook {
return []ent.Hook{
hooks.AuditHook,
}
}
// A AuditHook is an example for audit-log hook.
func AuditHook(next ent.Mutator) ent.Mutator {
// AuditLogger wraps the methods that are shared between all mutations of
// schemas that embed the AuditLog mixin. The variable "exists" is true, if
// the field already exists in the mutation (e.g. was set by a different hook).
type AuditLogger interface {
SetCreatedAt(time.Time)
CreatedAt() (value time.Time, exists bool)
SetCreatedBy(int)
CreatedBy() (id int, exists bool)
SetUpdatedAt(time.Time)
UpdatedAt() (value time.Time, exists bool)
SetUpdatedBy(int)
UpdatedBy() (id int, exists bool)
}
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
ml, ok := m.(AuditLogger)
if !ok {
return nil, fmt.Errorf("unexpected audit-log call from mutation type %T", m)
}
usr, err := viewer.UserFromContext(ctx)
if err != nil {
return nil, err
}
switch op := m.Op(); {
case op.Is(ent.OpCreate):
ml.SetCreatedAt(time.Now())
if _, exists := ml.CreatedBy(); !exists {
ml.SetCreatedBy(usr.ID)
}
case op.Is(ent.OpUpdateOne | ent.OpUpdate):
ml.SetUpdatedAt(time.Now())
if _, exists := ml.UpdatedBy(); !exists {
ml.SetUpdatedBy(usr.ID)
}
}
return next.Mutate(ctx, m)
})
}
```