Files
amsterdam/ui/amcontext.go
T

315 lines
8.4 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 (
"bytes"
"mime/multipart"
"net/http"
"strconv"
"time"
"git.erbosoft.com/amy/amsterdam/config"
"git.erbosoft.com/amy/amsterdam/database"
"github.com/CloudyKit/jet/v6"
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
)
// AmContext is the interface for Amsterdam's wrapper context that exposes the required functionality.
type AmContext interface {
ClearLoginCookie()
ClearSession()
CurrentUser() *database.User
CurrentUserId() int32
FormField(string) string
FormFieldInt(string) (int, error)
FormFieldIsSet(string) bool
FormFile(string) (*multipart.FileHeader, error)
RC() int
OutputType() string
Parameter(string) string
RemoteIP() string
ReplaceUser(*database.User)
SaveSession() error
SubRender(string) ([]byte, error)
SetLoginCookie(string)
SetOutputType(string)
SetRC(int)
GetScratch(string) any
SetScratch(string, any)
URLParam(string) string
URLParamInt(string) (int, error)
URLPath() string
VarMap() jet.VarMap
}
// amContext is the internal structure that implements AmContext.
type amContext struct {
echoContext echo.Context
httprc int
rendervars jet.VarMap
outputType string
scratchpad map[string]any
session *sessions.Session
}
// ClearLoginCookie overwrites and removes the login cookie.
func (c *amContext) ClearLoginCookie() {
cookie := new(http.Cookie)
cookie.Name = config.GlobalConfig.Site.LoginCookieName
cookie.Value = ""
cookie.Path = "/"
cookie.Expires = time.Now()
c.echoContext.SetCookie(cookie)
}
// ClearSession clears the current session.
func (c *amContext) ClearSession() {
AmResetSession(c.session)
}
// CurrentUser returns the current user from the session.
func (c *amContext) CurrentUser() *database.User {
u, err := database.AmGetUser(AmSessionUid(c.session))
if err != nil {
log.Errorf("unable to retrieve current user")
}
return u
}
// CurrentUserId returns the current user ID.
func (c *amContext) CurrentUserId() int32 {
return AmSessionUid(c.session)
}
/* FormField returns the value of a form field from the request.
* Parameters:
* name - The name of the field to retrieve.
* Returns:
* The value given to that named field.
*/
func (c *amContext) FormField(name string) string {
return c.echoContext.FormValue(name)
}
/* FormFieldInt returns the value of a form field from the request, as an integer.
* Parameters:
* name - The name of the field to retrieve.
* Returns:
* The value given to that named field.
* Standard Go error status.
*/
func (c *amContext) FormFieldInt(name string) (int, error) {
return strconv.Atoi(c.echoContext.FormValue(name))
}
/* FormFieldIsSet returns true if a given form field is set.
* Parameters:
* name - The name of the field to test.
* Returns:
* true if the field is set, false if not.
*/
func (c *amContext) FormFieldIsSet(name string) bool {
req := c.echoContext.Request()
if req.Form == nil {
_ = req.FormValue(name) // force form to be loaded
}
return req.Form.Has(name)
}
// FormFile returns a "file" parameter from a multipart upload form.
func (c *amContext) FormFile(name string) (*multipart.FileHeader, error) {
return c.echoContext.FormFile(name)
}
// RC returns the HTTP result code for the current operation.
func (c *amContext) RC() int {
return c.httprc
}
// OutputType returns the MIME output type set for the current operation.
func (c *amContext) OutputType() string {
return c.outputType
}
/* Parameter returns the value of a parameter (query parameter or form field) from the request.
* Parameters:
* name - The name of the field to retrieve.
* Returns:
* The value given to that named field.
*/
func (c *amContext) Parameter(name string) string {
rc := c.echoContext.QueryParam(name)
if rc == "" && c.echoContext.Request().Method == "POST" {
rc = c.echoContext.FormValue(name)
}
return rc
}
// RemoteIP returns the remote IP address.
func (c *amContext) RemoteIP() string {
return c.echoContext.RealIP()
}
/* ReplaceUser replaces the current user in the context.
* Parameters:
* u - New user to associate with the context.
*/
func (c *amContext) ReplaceUser(u *database.User) {
AmSetSessionUser(c.session, u)
}
// SaveSession saves the session link to cookies.
func (c *amContext) SaveSession() error {
return c.session.Save(c.echoContext.Request(), c.echoContext.Response())
}
// Scratchpad returns the per-request scratchpad for values.
func (c *amContext) Scratchpad() map[string]any {
if c.scratchpad == nil {
c.scratchpad = make(map[string]any)
}
return c.scratchpad
}
/* SubRender renders a subtemplate to the output.
* Parameters:
* name = The name of the template to be rendered.
* Returns:
* Byte array with the rendered data to be output
* Standard Go error status
*/
func (c *amContext) SubRender(name string) ([]byte, error) {
view, err := views.GetTemplate(name)
if err != nil {
log.Errorf("unable to load template \"%s\": %v", name, err)
return nil, err
}
buf := new(bytes.Buffer)
err = view.Execute(buf, c.VarMap(), c)
return buf.Bytes(), err
}
/* SetLoginCookie adds the login cookie to the result output.
* Parameters:
* auth - The auth string to set.
*/
func (c *amContext) SetLoginCookie(auth string) {
cookie := new(http.Cookie)
cookie.Name = config.GlobalConfig.Site.LoginCookieName
cookie.Value = auth
cookie.Path = "/"
cookie.Expires = time.Now().AddDate(0, 0, config.GlobalConfig.Site.LoginCookieAge)
c.echoContext.SetCookie(cookie)
}
// SetOutputType sets the MIME output type for the current operation.
func (c *amContext) SetOutputType(typ string) {
c.outputType = typ
}
// SetRC sets the HTTP result code for the current operation.
func (c *amContext) SetRC(rc int) {
c.httprc = rc
}
// GetScratch returns a value in the per-request scratchpad.
func (c *amContext) GetScratch(name string) any {
if c.scratchpad == nil {
return nil
}
return c.scratchpad[name]
}
// SetScratch sets a value in the per-request scratchpad.
func (c *amContext) SetScratch(name string, val any) {
if c.scratchpad == nil {
c.scratchpad = make(map[string]any)
}
c.scratchpad[name] = val
}
// URLParam returns the value of a URL parameter.
func (c *amContext) URLParam(name string) string {
return c.echoContext.Param(name)
}
// URLParamINt returns the value of a URL parameter parsed as an integer.
func (c *amContext) URLParamInt(name string) (int, error) {
return strconv.Atoi(c.echoContext.Param(name))
}
// URLPath returns the path component of the request URL.
func (c *amContext) URLPath() string {
return c.echoContext.Request().URL.Path
}
// VarMap provides access to the Jet variable map for setting variable data.
func (c *amContext) VarMap() jet.VarMap {
return c.rendervars
}
// defoptions is the default options for the HTTP session.
var defoptions *sessions.Options = &sessions.Options{
Path: "/",
MaxAge: 86400,
HttpOnly: true,
}
/* NewAmContext creates a new AmContext wrapping the Echo context.
* Parameters:
* ctxt - The Echo context to be wrapped.
* Returns:
* A new Amsterdam context wrapping that context.
* Standard Go error status.
*/
func NewAmContext(ctxt echo.Context) (AmContext, error) {
rc := amContext{
echoContext: ctxt,
httprc: http.StatusOK,
rendervars: make(jet.VarMap),
outputType: "",
scratchpad: nil,
}
ctxt.Set("amsterdam_context", &rc)
sess, err := session.Get("AMSTERDAM_SESSION", ctxt)
if err == nil {
rc.session = sess
sess.Options = defoptions
if sess.IsNew {
AmSessionFirstTime(sess)
} else {
AmHitSession(sess)
}
}
return &rc, err
}
/* AmContextFromEchoContext returns the AmContext associated with an Echo context.
* Parameters:
* ctxt - The Echo context to have the AmContext extracted.
* Returns:
* The associated AmContext, or nil if there is none.
*/
func AmContextFromEchoContext(ctxt echo.Context) AmContext {
myctxt := ctxt.Get("amsterdam_context")
if myctxt != nil {
rc, ok := myctxt.(AmContext)
if ok {
return rc
}
}
return nil
}