- translatable types lift, store and hydrate through the ML path
(isMLField); Decode and Load run per locale and notices carry the locale
- on update a value equal to the served value (Load of the stored host
value or exact translation) is neither decoded nor written
- FieldWrite, FieldWritesFromContext and WithFieldWrites expose what a save
writes to the Form hooks
- list columns may use a plugin alias and show the type's ListValue
- POST .../{id}/fields/{field}/upload hands the file to the type's Upload
(guard, X-Requested-With, permissions, field context, record scope, body
cap); OpenAPI, route inventories, README and docs updated
121 lines
4.2 KiB
Go
121 lines
4.2 KiB
Go
package cabana
|
|
|
|
import (
|
|
"context"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type txContextKey struct{}
|
|
|
|
// withTx returns ctx carrying the transaction a write is running in.
|
|
func withTx(ctx context.Context, tx *gorm.DB) context.Context {
|
|
if tx == nil {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, txContextKey{}, tx)
|
|
}
|
|
|
|
// TxFromContext returns the database transaction a write route is running in,
|
|
// for the context handed to a lifecycle hook (pact.FormBeforeCreate and the
|
|
// other Form and Relation hooks) or a scope (pact.FormExtendQuery) that runs
|
|
// inside it. A hook that has to read the database should use this handle
|
|
// instead of the application pool: it then sees the same snapshot as the write
|
|
// and does not take a second connection while the transaction holds row locks.
|
|
// The handle is valid only until the hook returns. The second result is false
|
|
// on routes that do not run in a transaction, such as the list.
|
|
func TxFromContext(ctx context.Context) (*gorm.DB, bool) {
|
|
if ctx == nil {
|
|
return nil, false
|
|
}
|
|
tx, ok := ctx.Value(txContextKey{}).(*gorm.DB)
|
|
return tx, ok && tx != nil
|
|
}
|
|
|
|
type virtualFieldsKey struct{}
|
|
|
|
// withVirtualFields returns ctx carrying the virtual field values of a save.
|
|
func withVirtualFields(ctx context.Context, values map[string]any) context.Context {
|
|
if values == nil {
|
|
values = map[string]any{}
|
|
}
|
|
return context.WithValue(ctx, virtualFieldsKey{}, values)
|
|
}
|
|
|
|
// VirtualFieldsFromContext returns the values an administrator submitted for
|
|
// the form's virtual fields (pact.FormVirtualFields), keyed by field name, for
|
|
// the context handed to a Form hook (pact.FormBeforeCreate,
|
|
// pact.FormAfterCreate, pact.FormBeforeUpdate, pact.FormAfterUpdate). Only
|
|
// fields that were present in the request body and whose `context` allows the
|
|
// operation are in the map, so a missing key means "not submitted". Values
|
|
// are scalars as decoded from the JSON body: a string, a bool, a json.Number
|
|
// or nil. The map is a copy. The second result is false outside a create or
|
|
// update save.
|
|
func VirtualFieldsFromContext(ctx context.Context) (map[string]any, bool) {
|
|
if ctx == nil {
|
|
return nil, false
|
|
}
|
|
values, ok := ctx.Value(virtualFieldsKey{}).(map[string]any)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out := make(map[string]any, len(values))
|
|
for name, value := range values {
|
|
out[name] = value
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
type fieldWritesKey struct{}
|
|
|
|
// FieldWrite is one value a save wrote for a plugin field type
|
|
// (pact.HasFormFieldTypes): Locale is the content locale of a translatable
|
|
// type's value and empty otherwise, Default is true when the value went to
|
|
// the host column, and Value is what the type's Decode returned.
|
|
type FieldWrite struct {
|
|
Locale string
|
|
Default bool
|
|
Value any
|
|
}
|
|
|
|
// withFieldWrites returns ctx carrying the plugin field values a save writes.
|
|
func withFieldWrites(ctx context.Context, writes map[string][]FieldWrite) context.Context {
|
|
if writes == nil {
|
|
writes = map[string][]FieldWrite{}
|
|
}
|
|
return context.WithValue(ctx, fieldWritesKey{}, writes)
|
|
}
|
|
|
|
// WithFieldWrites returns ctx carrying writes as a save would, so a test can
|
|
// call a Form hook that reads FieldWritesFromContext. The map is copied.
|
|
func WithFieldWrites(ctx context.Context, writes map[string][]FieldWrite) context.Context {
|
|
return withFieldWrites(ctx, copyFieldWrites(writes))
|
|
}
|
|
|
|
// FieldWritesFromContext returns the values a create or update save
|
|
// actually writes for the form's plugin field types, keyed by field name, for
|
|
// the context handed to a Form hook (pact.FormBeforeCreate,
|
|
// pact.FormAfterCreate, pact.FormBeforeUpdate, pact.FormAfterUpdate). A field
|
|
// or locale whose submitted value equals the value the form was served is
|
|
// not written and not listed; a translatable type lists one entry per
|
|
// written locale. The map is a copy. The second result is false outside a
|
|
// create or update save.
|
|
func FieldWritesFromContext(ctx context.Context) (map[string][]FieldWrite, bool) {
|
|
if ctx == nil {
|
|
return nil, false
|
|
}
|
|
writes, ok := ctx.Value(fieldWritesKey{}).(map[string][]FieldWrite)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return copyFieldWrites(writes), true
|
|
}
|
|
|
|
func copyFieldWrites(in map[string][]FieldWrite) map[string][]FieldWrite {
|
|
out := make(map[string][]FieldWrite, len(in))
|
|
for field, list := range in {
|
|
out[field] = append([]FieldWrite(nil), list...)
|
|
}
|
|
return out
|
|
}
|