feat(12.1-01): declared bulk actions on admin lists
- pact.HasAdminBulkActions with AdminBulkAction, its input and result
- config_list.yaml bulkActions, compiled fail-loud, needs showCheckboxes
- POST .../{controller}/bulk/{action}: ids resolved and locked through the
list scope in one transaction; partial selection is 409
- list schema offers declared actions per principal, with confirm text
- admin SPA bulk actions menu with confirm, busy state and failure toasts
- acme.roster fixture, tracer test, OpenAPI, TS types, dist, READMEs, docs
This commit is contained in:
88
modules/cabana/example_actions_test.go
Normal file
88
modules/cabana/example_actions_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package cabana_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
)
|
||||
|
||||
// Person is the model behind the acme.roster people controller.
|
||||
type Person struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
Email string `gorm:"column:email"`
|
||||
Active bool `gorm:"column:active"`
|
||||
}
|
||||
|
||||
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.
|
||||
type PeopleController struct{}
|
||||
|
||||
var (
|
||||
_ pact.AdminController = PeopleController{}
|
||||
_ pact.AdminRecordSource = PeopleController{}
|
||||
_ pact.HasAdminBulkActions = PeopleController{}
|
||||
)
|
||||
|
||||
func (PeopleController) ID() string { return "acme.roster.people" }
|
||||
func (PeopleController) ModelName() string { return "Person" }
|
||||
func (PeopleController) ConfigDir() string { return "controllers/people" }
|
||||
func (PeopleController) NewRecord() any { return &Person{} }
|
||||
|
||||
func (PeopleController) RequiredPermissions() []string {
|
||||
return []string{"acme.roster.access"}
|
||||
}
|
||||
|
||||
// AdminBulkActions registers the actions config_list.yaml offers under
|
||||
// bulkActions. Run receives the selected records, already loaded and locked
|
||||
// through the list scope, and writes through the request's transaction.
|
||||
func (PeopleController) AdminBulkActions() []pact.AdminBulkAction {
|
||||
return []pact.AdminBulkAction{{
|
||||
Name: "activate",
|
||||
Label: "acme.roster::lang.people.activate",
|
||||
Confirm: "acme.roster::lang.people.activate_confirm",
|
||||
Permissions: []string{"acme.roster.manage"},
|
||||
Run: func(ctx context.Context, in pact.AdminBulkActionInput) (pact.AdminBulkActionResult, error) {
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return pact.AdminBulkActionResult{}, errors.New("no transaction")
|
||||
}
|
||||
changed := 0
|
||||
for _, record := range in.Records {
|
||||
person := record.(*Person)
|
||||
if person.Active {
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(person).Update("active", true).Error; err != nil {
|
||||
return pact.AdminBulkActionResult{}, err
|
||||
}
|
||||
changed++
|
||||
}
|
||||
return pact.AdminBulkActionResult{Affected: changed}, nil
|
||||
},
|
||||
}, {
|
||||
Name: "archive",
|
||||
Label: "acme.roster::lang.people.archive",
|
||||
Run: func(ctx context.Context, in pact.AdminBulkActionInput) (pact.AdminBulkActionResult, error) {
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return pact.AdminBulkActionResult{}, errors.New("no transaction")
|
||||
}
|
||||
for _, record := range in.Records {
|
||||
if err := tx.Delete(record).Error; err != nil {
|
||||
return pact.AdminBulkActionResult{}, err
|
||||
}
|
||||
}
|
||||
return pact.AdminBulkActionResult{
|
||||
Message: "acme.roster::lang.people.archived",
|
||||
Affected: len(in.Records),
|
||||
}, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
Reference in New Issue
Block a user