diff --git a/errors.go b/errors.go index 54003e7..a8f7b90 100644 --- a/errors.go +++ b/errors.go @@ -11,6 +11,7 @@ package main import ( "errors" + "fmt" "net/http" "git.erbosoft.com/amy/amsterdam/ui" @@ -42,7 +43,6 @@ var EPARAM error = errors.New("no parameters specified") * Returns: * Command string dictating what to be rendered. * Data as a parameter for the command string. - * Standard Go error status. */ func NotImplPage(ctxt ui.AmContext) (string, any) { ctxt.SetLeftMenu("top") @@ -51,17 +51,31 @@ func NotImplPage(ctxt ui.AmContext) (string, any) { return "framed", "notimpl.jet" } +/* AmNotFoundHandler handles all paths that are "not found" in the application. + * Parameters: + * ctxt - The AmContext for the request. + * Returns: + * Command string dictating what to be rendered. + * Data as a parameter for the command string. + */ +func AmNotFoundHandler(ctxt ui.AmContext) (string, any) { + log.Infof("-> AmNotFoundHandler on path %s", ctxt.URLPath()) + return "error", fmt.Errorf("Path not found: %s", ctxt.URLPath()) +} + /* AmErrorHandler handles all the mundane HTTP errors generated by the Echo engine. * Parameters: * err - The error to be handled. * c - The Echo context error is being handled on. */ func AmErrorHandler(err error, c echo.Context) { + log.Infof("-> AmErrorHandler on path %s", c.Request().URL.Path) if c.Response().Committed { return } - amctxt := ui.AmContextFromEchoContext(c) - cerr := ui.AmSendPageData(c, amctxt, "error", err) + cerr := ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) { + return "error", err + }) if cerr != nil { log.Errorf("Error rendering error (%v): %v", err, cerr) } diff --git a/main.go b/main.go index 5a611e2..839f661 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ func setupEcho() *echo.Echo { // This is the set of all middleware functions used by the UI, as opposed to other things. uiset := []echo.MiddlewareFunc{ui.SessionStoreInjector, ui.ContextCreator, ui.IPBanTest, ui.CookieLoginTest} + e.RouteNotFound("/*", ui.AmWrap(AmNotFoundHandler), uiset...) e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...) e.GET("/img/*", ui.AmServeImage) e.GET("/static/*", ui.AmStaticFileHandler()) diff --git a/ui/amcontext.go b/ui/amcontext.go index 5e2ecae..3b8c791 100644 --- a/ui/amcontext.go +++ b/ui/amcontext.go @@ -549,34 +549,37 @@ func newContext(ctxt echo.Context) (*amContext, error) { rc.echoContext = ctxt ctxt.Set("__amsterdam_context", rc) - store := ctxt.Get("AmSessionStore").(AmSessionStore) - sess, err := store.Get(ctxt.Request(), "AMSTERDAM_SESSION") - if err == nil { - rc.session = sess - sess.SetOptions(defoptions) - if sess.IsNew() { - sess.FirstTime(ctxt.Request().Context()) - } else { - sess.Hit() - } - } - id, ok := sess.Uid() - if ok { - rc.user, err = database.AmGetUser(ctxt.Request().Context(), id) + stmp := ctxt.Get("AmSessionStore") + if stmp != nil { + store := stmp.(AmSessionStore) + sess, err := store.Get(ctxt.Request(), "AMSTERDAM_SESSION") if err == nil { - rc.effectiveLevel = rc.user.BaseLevel + rc.session = sess + sess.SetOptions(defoptions) + if sess.IsNew() { + sess.FirstTime(ctxt.Request().Context()) + } else { + sess.Hit() + } + } + id, ok := sess.Uid() + if ok { + rc.user, err = database.AmGetUser(ctxt.Request().Context(), id) + if err == nil { + rc.effectiveLevel = rc.user.BaseLevel + } else { + rc.user = nil + rc.effectiveLevel = database.AmRole("NotInList").Level() + } } else { rc.user = nil rc.effectiveLevel = database.AmRole("NotInList").Level() } - } else { - rc.user = nil - rc.effectiveLevel = database.AmRole("NotInList").Level() - } - if rc.user != nil && !rc.user.IsAnon { - cp, ok := sess.Get("lastCommunity") - if ok { - rc.SetCommunityContext(fmt.Sprintf("%d", cp)) + if rc.user != nil && !rc.user.IsAnon { + cp, ok := sess.Get("lastCommunity") + if ok { + rc.SetCommunityContext(fmt.Sprintf("%d", cp)) + } } } return rc, err diff --git a/ui/render_wrap.go b/ui/render_wrap.go index 87bbaa8..ada7b24 100644 --- a/ui/render_wrap.go +++ b/ui/render_wrap.go @@ -114,11 +114,16 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an case "top": menus[0] = AmMenu("top") case "community": - md, err := AmBuildCommunityMenu(ctxt.Request().Context(), amctxt.CurrentCommunity()) - if err != nil { - return err + comm := amctxt.CurrentCommunity() + if comm != nil { + md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm) + if err != nil { + return err + } + menus[0] = md + } else { + menus[0] = AmMenu("top") } - menus[0] = md default: return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu()) } @@ -175,3 +180,40 @@ func AmWrap(myfunc AmPageFunc) echo.HandlerFunc { return nil } } + +// AmWithTempContext runs a page function with a temporary context. Used in error handling. +func AmWithTempContext(c echo.Context, fn AmPageFunc) error { + var ctxt AmContext = nil + myctxt := c.Get("__amsterdam_context") + if myctxt != nil { + ac, ok := myctxt.(*amContext) + if ok { + ctxt = ac + ac.echoContext = c + } + } + if ctxt == nil { + ac, err := newContext(c) + if err != nil { + return err + } + ctxt = ac + defer func() { + amContextRecycleBin <- ac + }() + } + + // Call the function + command, arg := fn(ctxt) + + // Add the dynamic headers. + c.Response().Header().Set("Pragma", "No-cache") + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("Expires", expireTime) + + if err := AmSendPageData(c, ctxt, command, arg); err != nil { + c.Logger().Errorf("Rendering error: %v", err) + return err + } + return nil +}