github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/page.go (about)

     1  // Package cutil - Content managed by Project Forge, see [projectforge.md] for details.
     2  package cutil
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"slices"
    10  	"time"
    11  
    12  	"github.com/samber/lo"
    13  
    14  	"github.com/kyleu/dbaudit/app"
    15  	"github.com/kyleu/dbaudit/app/controller/cmenu"
    16  	"github.com/kyleu/dbaudit/app/lib/filter"
    17  	"github.com/kyleu/dbaudit/app/lib/menu"
    18  	"github.com/kyleu/dbaudit/app/lib/telemetry"
    19  	"github.com/kyleu/dbaudit/app/lib/theme"
    20  	"github.com/kyleu/dbaudit/app/lib/user"
    21  	"github.com/kyleu/dbaudit/app/util"
    22  )
    23  
    24  const (
    25  	DefaultSearchPath  = "/search"
    26  	DefaultProfilePath = "/profile"
    27  	defaultIcon        = "app"
    28  )
    29  
    30  var (
    31  	defaultRootTitleAppend = util.GetEnv("app_display_name_append")
    32  	defaultRootTitle       = func() string {
    33  		if tmp := util.GetEnv("app_display_name"); tmp != "" {
    34  			return tmp
    35  		}
    36  		return util.AppName
    37  	}()
    38  )
    39  
    40  type PageState struct {
    41  	Action         string            `json:"action,omitempty"`
    42  	Title          string            `json:"title,omitempty"`
    43  	Description    string            `json:"description,omitempty"`
    44  	Method         string            `json:"method,omitempty"`
    45  	URI            *url.URL          `json:"-"`
    46  	Menu           menu.Items        `json:"menu,omitempty"`
    47  	Breadcrumbs    cmenu.Breadcrumbs `json:"breadcrumbs,omitempty"`
    48  	Flashes        []string          `json:"flashes,omitempty"`
    49  	Session        util.ValueMap     `json:"-"`
    50  	Profile        *user.Profile     `json:"profile,omitempty"`
    51  	Accounts       user.Accounts     `json:"accounts,omitempty"`
    52  	Authed         bool              `json:"authed,omitempty"`
    53  	Admin          bool              `json:"admin,omitempty"`
    54  	Params         filter.ParamSet   `json:"params,omitempty"`
    55  	Icons          []string          `json:"icons,omitempty"`
    56  	DefaultNavIcon string            `json:"defaultNavIcon,omitempty"`
    57  	RootIcon       string            `json:"rootIcon,omitempty"`
    58  	RootPath       string            `json:"rootPath,omitempty"`
    59  	RootTitle      string            `json:"rootTitle,omitempty"`
    60  	SearchPath     string            `json:"searchPath,omitempty"`
    61  	ProfilePath    string            `json:"profilePath,omitempty"`
    62  	HideMenu       bool              `json:"hideMenu,omitempty"`
    63  	ForceRedirect  string            `json:"forceRedirect,omitempty"`
    64  	HeaderContent  string            `json:"headerContent,omitempty"`
    65  	Browser        string            `json:"browser,omitempty"`
    66  	BrowserVersion string            `json:"browserVersion,omitempty"`
    67  	OS             string            `json:"os,omitempty"`
    68  	OSVersion      string            `json:"osVersion,omitempty"`
    69  	Platform       string            `json:"platform,omitempty"`
    70  	Data           any               `json:"data,omitempty"`
    71  	Started        time.Time         `json:"started,omitempty"`
    72  	RenderElapsed  float64           `json:"renderElapsed,omitempty"`
    73  	RequestBody    []byte            `json:"-"`
    74  	Logger         util.Logger       `json:"-"`
    75  	Context        context.Context   `json:"-"` //nolint:containedctx // properly closed, never directly used
    76  	Span           *telemetry.Span   `json:"-"`
    77  }
    78  
    79  func (p *PageState) AddIcon(keys ...string) {
    80  	lo.ForEach(keys, func(k string, _ int) {
    81  		if !lo.Contains(p.Icons, k) {
    82  			p.Icons = append(p.Icons, k)
    83  		}
    84  	})
    85  	slices.Sort(p.Icons)
    86  }
    87  
    88  func (p *PageState) TitleString() string {
    89  	if p.Title == "" {
    90  		return util.AppName
    91  	}
    92  	return fmt.Sprintf("%s - %s", p.Title, util.AppName)
    93  }
    94  
    95  func (p *PageState) Username() string {
    96  	return p.Profile.Name
    97  }
    98  
    99  func (p *PageState) AuthString() string {
   100  	n := p.Profile.String()
   101  	msg := fmt.Sprintf("signed in as %s", n)
   102  	if len(p.Accounts) == 0 {
   103  		if n == user.DefaultProfile.Name {
   104  			return "click to sign in"
   105  		}
   106  		return msg
   107  	}
   108  	return fmt.Sprintf("%s using [%s]", msg, p.Accounts.TitleString())
   109  }
   110  
   111  func (p *PageState) Clean(_ *http.Request, as *app.State) error {
   112  	if p.Profile != nil && p.Profile.Theme == "" {
   113  		p.Profile.Theme = theme.Default.Key
   114  	}
   115  	if p.RootIcon == "" {
   116  		p.RootIcon = defaultIcon
   117  	}
   118  	if p.RootPath == "" {
   119  		p.RootPath = "/"
   120  	}
   121  	if p.RootTitle == "" {
   122  		p.RootTitle = defaultRootTitle
   123  	}
   124  	if defaultRootTitleAppend != "" {
   125  		p.RootTitle += " " + defaultRootTitleAppend
   126  	}
   127  	if p.SearchPath == "" {
   128  		p.SearchPath = DefaultSearchPath
   129  	}
   130  	if p.ProfilePath == "" {
   131  		p.ProfilePath = DefaultProfilePath
   132  	}
   133  	if len(p.Menu) == 0 {
   134  		m, data, err := cmenu.MenuFor(p.Context, p.Authed, p.Admin, p.Profile, p.Params, as, p.Logger)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		if data != nil && p.Data == nil {
   139  			p.Data = data
   140  		}
   141  		p.Menu = m
   142  	}
   143  	return nil
   144  }
   145  
   146  func (p *PageState) Close() {
   147  	if p.Span != nil {
   148  		p.Span.Complete()
   149  	}
   150  }
   151  
   152  func (p *PageState) LogError(msg string, args ...any) {
   153  	p.Logger.Errorf(msg, args...)
   154  }
   155  
   156  func (p *PageState) ClassDecl() string {
   157  	ret := &util.StringSlice{}
   158  	if p.Profile.Mode != "" {
   159  		ret.Push(p.Profile.ModeClass())
   160  	}
   161  	if p.Browser != "" {
   162  		ret.Push("browser-" + p.Browser)
   163  	}
   164  	if p.OS != "" {
   165  		ret.Push("os-" + p.OS)
   166  	}
   167  	if p.Platform != "" {
   168  		ret.Push("platform-" + p.Platform)
   169  	}
   170  	if ret.Empty() {
   171  		return ""
   172  	}
   173  	classes := ret.Join(" ")
   174  	return fmt.Sprintf(` class=%q`, classes)
   175  }
   176  
   177  func (p *PageState) SetTitleAndData(title string, data any) {
   178  	p.Title = title
   179  	p.Data = data
   180  }