196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
/*
|
|
* Amsterdam Web Communities System
|
|
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
|
|
package ui
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.erbosoft.com/amy/amsterdam/database"
|
|
"git.erbosoft.com/amy/amsterdam/util"
|
|
lru "github.com/hashicorp/golang-lru"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// MenuItem represents an item within a menu definition.
|
|
type MenuItem struct {
|
|
Text string `yaml:"text"`
|
|
Link string `yaml:"link"`
|
|
Disabled bool `yaml:"disabled"`
|
|
Hazard bool `yaml:"hazard"`
|
|
Permission string `yaml:"permission"`
|
|
Ifdef string `yaml:"ifdef"`
|
|
P *MenuDefinition
|
|
}
|
|
|
|
// Show checks permissions to see if we can display the menu item.
|
|
func (mi *MenuItem) Show(ctxt AmContext) bool {
|
|
if mi.Permission == "" {
|
|
return true
|
|
}
|
|
u := ctxt.CurrentUser()
|
|
var eperm uint16
|
|
switch mi.P.PermSet {
|
|
case "user":
|
|
eperm = u.BaseLevel
|
|
case "community":
|
|
eperm = ctxt.EffectiveLevel()
|
|
default:
|
|
eperm = database.AmRole("NotInList").Level()
|
|
}
|
|
if util.IsNumeric(mi.Permission) {
|
|
v, _ := strconv.Atoi(mi.Permission)
|
|
return uint16(v) <= eperm
|
|
}
|
|
switch mi.P.PermSet {
|
|
case "user":
|
|
return database.AmTestPermission(mi.Permission, eperm)
|
|
case "community":
|
|
return ctxt.CurrentCommunity().TestPermission(mi.Permission, eperm)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MenuDefinition represents a full menu definition.
|
|
type MenuDefinition struct {
|
|
ID string `yaml:"id"`
|
|
Title string `yaml:"title"`
|
|
Subtitle string `yaml:"subtitle"`
|
|
PermSet string `yaml:"permSet"`
|
|
Warning string `yaml:"warning"`
|
|
Items []MenuItem `yaml:"items"`
|
|
Tag string
|
|
}
|
|
|
|
// FilterCommunity creates a copy of this menu filtered to the specified community.
|
|
func (mdef *MenuDefinition) FilterCommunity(comm *database.Community) *MenuDefinition {
|
|
newmd := MenuDefinition{
|
|
ID: mdef.ID,
|
|
Title: mdef.Title,
|
|
Subtitle: strings.ReplaceAll(mdef.Subtitle, "[CNAME]", comm.Name),
|
|
PermSet: mdef.PermSet,
|
|
Warning: mdef.Warning,
|
|
Items: make([]MenuItem, len(mdef.Items)),
|
|
Tag: mdef.Tag,
|
|
}
|
|
for i, it := range mdef.Items {
|
|
newmd.Items[i].Text = it.Text
|
|
newmd.Items[i].Link = strings.ReplaceAll(it.Link, "[CID]", comm.Alias)
|
|
newmd.Items[i].Disabled = it.Disabled
|
|
newmd.Items[i].Hazard = it.Hazard
|
|
newmd.Items[i].Permission = it.Permission
|
|
newmd.Items[i].Ifdef = it.Ifdef
|
|
newmd.Items[i].P = &newmd
|
|
}
|
|
return &newmd
|
|
}
|
|
|
|
// MenuDefs represents the set of all menu definitions.
|
|
type MenuDefs struct {
|
|
D []MenuDefinition `yaml:"menudefs"`
|
|
table map[string]*MenuDefinition
|
|
}
|
|
|
|
//go:embed menudefs.yaml
|
|
var initMenuData []byte
|
|
|
|
// menuDefinitions gives the menu definitions.
|
|
var menuDefinitions MenuDefs
|
|
|
|
// Cache of community menus.
|
|
var menuCache *lru.Cache
|
|
|
|
// Mutex controlling access to the cache.
|
|
var menuCacheMutex sync.Mutex
|
|
|
|
// init loads the menu definitions.
|
|
func init() {
|
|
var err error
|
|
if menuCache, err = lru.New(100); err != nil {
|
|
panic(err)
|
|
}
|
|
if err = yaml.Unmarshal(initMenuData, &menuDefinitions); err != nil {
|
|
panic(err) // can't happen
|
|
}
|
|
menuDefinitions.table = make(map[string]*MenuDefinition)
|
|
for i, d := range menuDefinitions.D {
|
|
menuDefinitions.table[d.ID] = &(menuDefinitions.D[i])
|
|
for j := range menuDefinitions.D[i].Items {
|
|
menuDefinitions.D[i].Items[j].P = &(menuDefinitions.D[i])
|
|
}
|
|
menuDefinitions.D[i].Tag = ""
|
|
}
|
|
}
|
|
|
|
// AmMenu returns a menu definition.
|
|
func AmMenu(name string) *MenuDefinition {
|
|
return menuDefinitions.table[name]
|
|
}
|
|
|
|
/* AmBuildCommunityMenu buids a community menu for the specified community.
|
|
* Parameters:
|
|
* comm - The community to build the menu for.
|
|
* Returns:
|
|
* The new menu definition.
|
|
* Standard Go error status.
|
|
*/
|
|
func AmBuildCommunityMenu(comm *database.Community) (*MenuDefinition, error) {
|
|
menuCacheMutex.Lock()
|
|
defer menuCacheMutex.Unlock()
|
|
m, ok := menuCache.Get(comm.Id)
|
|
if ok {
|
|
return m.(*MenuDefinition), nil
|
|
}
|
|
sdef, err := database.AmGetCommunityServices(comm.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
slices.SortFunc(sdef, func(a, b *database.ServiceDef) int {
|
|
return a.LinkSequence - b.LinkSequence
|
|
})
|
|
mia := make([]MenuItem, len(sdef))
|
|
md := MenuDefinition{
|
|
ID: "community",
|
|
Title: comm.Name,
|
|
PermSet: "community",
|
|
Tag: "community",
|
|
Items: mia,
|
|
}
|
|
for i, sd := range sdef {
|
|
mia[i].Text = sd.Title
|
|
mia[i].Link = strings.ReplaceAll(sd.Link, "[CID]", comm.Alias)
|
|
mia[i].Disabled = false
|
|
mia[i].P = &md
|
|
if sd.RequirePermission == "" {
|
|
if sd.RequireRole == "" {
|
|
mia[i].Permission = ""
|
|
} else {
|
|
mia[i].Permission = fmt.Sprintf("%d", database.AmRole(sd.RequireRole).Level())
|
|
}
|
|
} else if sd.RequireRole == "" {
|
|
mia[i].Permission = sd.RequirePermission
|
|
} else {
|
|
v1 := comm.PermissionLevel(sd.RequirePermission)
|
|
v2 := database.AmRole(sd.RequireRole).Level()
|
|
if v2 > v1 {
|
|
v1 = v2
|
|
}
|
|
mia[i].Permission = fmt.Sprintf("%d", v1)
|
|
}
|
|
}
|
|
menuCache.Add(comm.Id, &md)
|
|
return &md, nil
|
|
}
|