Files
summercms/modules/cabana/tx_context.go
Jakub Zych a1c6bb1ce6 feat(12.1-02): password and form-only fields, rules per operation and preset
- pact.FormVirtualFields lists form fields that are not model columns: never
  bound, filled or projected; their values reach the Form hooks through
  cabana.VirtualFieldsFromContext when the field's context allows the operation
- type: password is a masked field that must be listed as virtual
- pact.FormRules supplies the rule set per operation and replaces the model's
  Rules() for admin saves; a rule on a virtual field sees the submitted value
- preset on a text field follows another text field on the create form
- SPA: PasswordField, preset handling in FormView, empty password left out of
  an update
- README, docs, OpenAPI document, TS types and dist updated
2026-10-05 10:35:08 +02:00

68 lines
2.3 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
}