feat(12.1-02): permissioneditor field in radio or checkbox mode
- type: permissioneditor with mode radio (1, -1) or checkbox (1); the controller serves the options per request through cabana.PermissionEditorProvider and reads and stores the values - a save answers 422 for a non-object, an unknown code or a value outside the mode's set and 403 for a changed locked code; stored codes that are not offered are kept - record responses carry the stored permissions as an object - SPA: PermissionEditorField with sections by tab, locked rows and a read-only mode for the preview - README, docs, OpenAPI document, TS types and dist updated
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
@@ -43,9 +45,12 @@ type rosterPerson struct {
|
||||
JoinedIP *string `gorm:"column:joined_ip"`
|
||||
// Password is a stored hash. The form's password field is virtual: the
|
||||
// controller's hooks derive this column from the submitted value.
|
||||
Password string `gorm:"column:password" json:"-"`
|
||||
Slug string `gorm:"column:slug"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at"`
|
||||
Password string `gorm:"column:password" json:"-"`
|
||||
Slug string `gorm:"column:slug"`
|
||||
// Permissions is the permission editor's storage: a JSON object of code
|
||||
// to value, or NULL.
|
||||
Permissions *string `gorm:"column:permissions"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at"`
|
||||
}
|
||||
|
||||
func (rosterPerson) TableName() string { return "roster_people" }
|
||||
@@ -258,6 +263,60 @@ func (rosterController) PartialData(_ context.Context, name string, record any)
|
||||
return rosterStatus{}, nil
|
||||
}
|
||||
|
||||
// rosterPermissionCodes are the permissions the people form offers: two tabs
|
||||
// and one permission without a tab. reports.export is locked for an
|
||||
// administrator without acme.roster.manage.
|
||||
var rosterPermissionCodes = []cabana.PermissionOption{
|
||||
{Code: "posts.edit", Label: "acme.roster::lang.permissions.posts_edit", Tab: "acme.roster::lang.permissions.tab_content", Comment: "acme.roster::lang.permissions.posts_edit_comment"},
|
||||
{Code: "posts.publish", Label: "acme.roster::lang.permissions.posts_publish", Tab: "acme.roster::lang.permissions.tab_content"},
|
||||
{Code: "reports.export", Label: "acme.roster::lang.permissions.reports_export", Tab: "acme.roster::lang.permissions.tab_reports"},
|
||||
{Code: "misc.beta", Label: "acme.roster::lang.permissions.misc_beta"},
|
||||
}
|
||||
|
||||
// AdminPermissionOptions serves the permission editor's options per
|
||||
// administrator.
|
||||
func (rosterController) AdminPermissionOptions(ctx context.Context, field string) ([]cabana.PermissionOption, error) {
|
||||
if field != "permissions" {
|
||||
return nil, fmt.Errorf("unknown permission field %s", field)
|
||||
}
|
||||
principal, _ := bouncer.User(ctx)
|
||||
out := append([]cabana.PermissionOption(nil), rosterPermissionCodes...)
|
||||
for i := range out {
|
||||
if out[i].Code == "reports.export" {
|
||||
out[i].Locked = !cabana.Allows(principal, []string{"acme.roster.manage"})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AdminPermissionValues reads the stored JSON object.
|
||||
func (rosterController) AdminPermissionValues(_ context.Context, _ string, record any) (map[string]int, error) {
|
||||
person := record.(*rosterPerson)
|
||||
out := map[string]int{}
|
||||
if person.Permissions == nil || *person.Permissions == "" {
|
||||
return out, nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(*person.Permissions), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AdminSetPermissionValues writes the JSON object onto the model; the save
|
||||
// writes the row.
|
||||
func (rosterController) AdminSetPermissionValues(ctx context.Context, _ string, record any, values map[string]int) error {
|
||||
if _, ok := cabana.TxFromContext(ctx); !ok {
|
||||
return fmt.Errorf("no transaction on the context")
|
||||
}
|
||||
raw, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text := string(raw)
|
||||
record.(*rosterPerson).Permissions = &text
|
||||
return nil
|
||||
}
|
||||
|
||||
// rosterLocked is the sentinel name of a person the roster's actions refuse.
|
||||
const rosterLocked = "Locked"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user