- controller golem15.user.organisations behind golem15.users.access_users - list and form (name, slug preset from the name, description, avatar) - members relation manager: link sets and unlink clears users.organisation_id - deleting an organisation releases its members first - Organisation gains Fillable, Rules, MorphName, AttachRelations, BeforeValidate and the Members relation; models.Slugify - navigation side item organisations - TestAdminOrganisations, TestAdminOrganisationMembers, TestSlugify
118 lines
4.3 KiB
Go
118 lines
4.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"regexp"
|
|
|
|
"git.golem15.com/golem15/sm-user-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// organisationSlugPattern is the PHP alpha_dash rule on the slug, kept to
|
|
// ASCII. lagoon.Validate has no alpha_dash token, so the Form before-hooks
|
|
// check it.
|
|
var organisationSlugPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
|
|
|
// membersRelation is the organisation's hasMany relation to its users.
|
|
const membersRelation = "members"
|
|
|
|
// organisationsAdminController is the Winter Organisations backend
|
|
// controller: the list, the form with the avatar, and the members relation
|
|
// manager. The screen and every write need golem15.users.access_users, as in
|
|
// PHP.
|
|
//
|
|
// The members manager sets and clears users.organisation_id and nothing
|
|
// else. It never writes users_groups.
|
|
type organisationsAdminController struct {
|
|
app appAccess
|
|
}
|
|
|
|
func (organisationsAdminController) ID() string { return "golem15.user.organisations" }
|
|
|
|
func (organisationsAdminController) ModelName() string { return `Golem15\User\Models\Organisation` }
|
|
|
|
func (organisationsAdminController) ConfigDir() string { return "controllers/organisations" }
|
|
|
|
func (organisationsAdminController) RequiredPermissions() []string {
|
|
return []string{PermissionAccessUsers}
|
|
}
|
|
|
|
func (organisationsAdminController) NewRecord() any { return &models.Organisation{} }
|
|
|
|
// FormBeforeCreate and FormBeforeUpdate check the slug's characters.
|
|
func (c organisationsAdminController) FormBeforeCreate(ctx context.Context, model any) error {
|
|
return c.requireSlugFormat(ctx, model)
|
|
}
|
|
|
|
func (c organisationsAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
|
|
return c.requireSlugFormat(ctx, model)
|
|
}
|
|
|
|
// requireSlugFormat answers 422 on slug when it holds anything but letters,
|
|
// digits, underscore and hyphen. An empty slug is left to the required rule.
|
|
func (c organisationsAdminController) requireSlugFormat(ctx context.Context, model any) error {
|
|
organisation, ok := model.(*models.Organisation)
|
|
if !ok || organisation == nil {
|
|
return errors.New("organisations admin: form model is not an organisation")
|
|
}
|
|
if organisation.Slug == "" || organisationSlugPattern.MatchString(organisation.Slug) {
|
|
return nil
|
|
}
|
|
message := "The slug may only contain letters, numbers, dashes and underscores."
|
|
if tr := c.app.translator(); tr != nil {
|
|
const key = "golem15.user::lang.organisation.slug_invalid"
|
|
if text := tr.Get(ctx, key, nil); text != "" && text != key {
|
|
message = text
|
|
}
|
|
}
|
|
return &cabana.ValidationError{Details: map[string]any{"slug": []string{message}}}
|
|
}
|
|
|
|
// AdminRelationContracts declares the members relation manager: the users
|
|
// whose organisation_id is the organisation. Linking sets the column and
|
|
// unlinking clears it; the column is nullable, so a user is never deleted.
|
|
func (organisationsAdminController) AdminRelationContracts() []cabana.RelationContract {
|
|
return []cabana.RelationContract{{
|
|
Name: membersRelation,
|
|
Kind: cabana.RelationHasMany,
|
|
NewRelated: func() any { return &models.User{} },
|
|
ForeignKey: "organisation_id",
|
|
Columns: map[string]string{
|
|
"name": "name",
|
|
"email": "email",
|
|
},
|
|
}}
|
|
}
|
|
|
|
// RelationExtendManageQuery fails closed: any relation name other than
|
|
// members has no candidates.
|
|
func (organisationsAdminController) RelationExtendManageQuery(_ context.Context, relation string, db *gorm.DB) *gorm.DB {
|
|
if db == nil {
|
|
return db
|
|
}
|
|
if relation != membersRelation {
|
|
return db.Where("1 = 0")
|
|
}
|
|
return db
|
|
}
|
|
|
|
// FormBeforeDelete releases the members of the organisation in the delete's
|
|
// transaction: organisation_id and organisation_role are cleared, on
|
|
// deactivated users too, so the delete does not fail on the users foreign key
|
|
// and no user is deleted.
|
|
func (organisationsAdminController) FormBeforeDelete(ctx context.Context, model any) error {
|
|
organisation, ok := model.(*models.Organisation)
|
|
if !ok || organisation == nil {
|
|
return errors.New("organisations admin: form model is not an organisation")
|
|
}
|
|
tx, ok := cabana.TxFromContext(ctx)
|
|
if !ok {
|
|
return errors.New("organisations admin: a delete needs its transaction")
|
|
}
|
|
return tx.Session(&gorm.Session{NewDB: true}).WithContext(ctx).Unscoped().
|
|
Model(&models.User{}).Where("organisation_id = ?", organisation.ID).
|
|
Updates(map[string]any{"organisation_id": nil, "organisation_role": nil}).Error
|
|
}
|