- 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
89 lines
2.8 KiB
Go
89 lines
2.8 KiB
Go
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
|
|
},
|
|
}}
|
|
}
|