feat(12.1-01): declared record actions with an applicability rule
- pact.HasAdminRecordActions with AdminRecordAction (Applies, Run)
- config_form.yaml recordActions, compiled fail-loud
- show response meta.actions lists the permitted actions that apply
- POST .../{controller}/{id}/actions/{action}: record loaded and locked
through the form scope; 404 out of scope, 409 when it does not apply
- RecordActions.vue with confirm and request flow (mounted by plan 02)
- roster fixture, smoke tests, OpenAPI, TS types, READMEs, docs
This commit is contained in:
@@ -14,6 +14,7 @@ type Person struct {
|
||||
Name string `gorm:"column:name"`
|
||||
Email string `gorm:"column:email"`
|
||||
Active bool `gorm:"column:active"`
|
||||
Banned bool `gorm:"column:banned"`
|
||||
}
|
||||
|
||||
func (Person) TableName() string { return "acme_roster_people" }
|
||||
@@ -21,13 +22,14 @@ func (Person) TableName() string { return "acme_roster_people" }
|
||||
// Fillable lists the columns the admin form may write.
|
||||
func (Person) Fillable() []string { return []string{"name", "email"} }
|
||||
|
||||
// PeopleController is an admin controller whose list offers bulk actions.
|
||||
// PeopleController is an admin controller with bulk and record actions.
|
||||
type PeopleController struct{}
|
||||
|
||||
var (
|
||||
_ pact.AdminController = PeopleController{}
|
||||
_ pact.AdminRecordSource = PeopleController{}
|
||||
_ pact.HasAdminBulkActions = PeopleController{}
|
||||
_ pact.AdminController = PeopleController{}
|
||||
_ pact.AdminRecordSource = PeopleController{}
|
||||
_ pact.HasAdminBulkActions = PeopleController{}
|
||||
_ pact.HasAdminRecordActions = PeopleController{}
|
||||
)
|
||||
|
||||
func (PeopleController) ID() string { return "acme.roster.people" }
|
||||
@@ -86,3 +88,44 @@ func (PeopleController) AdminBulkActions() []pact.AdminBulkAction {
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// AdminRecordActions registers the actions config_form.yaml offers under
|
||||
// recordActions. Applies decides whether an action fits the record's current
|
||||
// state; Run receives the record loaded and locked through the form scope.
|
||||
func (PeopleController) AdminRecordActions() []pact.AdminRecordAction {
|
||||
return []pact.AdminRecordAction{{
|
||||
Name: "activate",
|
||||
Label: "acme.roster::lang.people.activate",
|
||||
Permissions: []string{"acme.roster.manage"},
|
||||
Applies: func(_ context.Context, record any) (bool, error) {
|
||||
return !record.(*Person).Active, nil
|
||||
},
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return pact.AdminRecordActionResult{}, errors.New("no transaction")
|
||||
}
|
||||
if err := tx.Model(in.Record).Update("active", true).Error; err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
return pact.AdminRecordActionResult{Message: "acme.roster::lang.people.activated"}, nil
|
||||
},
|
||||
}, {
|
||||
Name: "reinstate",
|
||||
Label: "acme.roster::lang.people.reinstate",
|
||||
Confirm: "acme.roster::lang.people.reinstate_confirm",
|
||||
Applies: func(_ context.Context, record any) (bool, error) {
|
||||
return record.(*Person).Banned, nil
|
||||
},
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return pact.AdminRecordActionResult{}, errors.New("no transaction")
|
||||
}
|
||||
if err := tx.Model(in.Record).Update("banned", false).Error; err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
return pact.AdminRecordActionResult{}, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user