Small fixes

This commit is contained in:
spsobole 2021-05-29 15:14:15 -06:00
parent baada0dae5
commit e59a973228
4 changed files with 187 additions and 24 deletions

View File

@ -7,6 +7,7 @@ import (
type AuthData struct { type AuthData struct {
User string User string
Group string Group string
Properties map[string]string
} }
type AuthManager interface { type AuthManager interface {

View File

@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"git.thirdmartini.com/pub/snap/auth" "git.thirdmartini.com/pub/snap/auth"
) )
@ -20,7 +21,9 @@ type Context struct {
type SnapContent struct { type SnapContent struct {
User string User string
UserProperties map[string]string
Theme string Theme string
Meta map[string]string
Content interface{} Content interface{}
} }
@ -43,14 +46,32 @@ func (c *Context) Error(code int, msg string) {
c.srv.renderError(c.w, code, msg) c.srv.renderError(c.w, code, msg)
} }
func (c *Context) ErrorObject(code int, obj interface{}) {
msg, err := json.Marshal(obj)
if err != nil {
c.Error(http.StatusInternalServerError, "Internal Server Error")
return
}
c.Error(code, string(msg))
}
func (c *Context) Reply(msg string) { func (c *Context) Reply(msg string) {
c.srv.reply(c.w, msg) c.srv.reply(c.w, msg)
} }
func (c *Context) ReplyWithHeaders(msg string, kv map[string]string) {
h := c.w.Header()
for k, v := range kv {
h.Add(k, v)
}
c.srv.reply(c.w, msg)
}
func (c *Context) ReplyObject(obj interface{}) { func (c *Context) ReplyObject(obj interface{}) {
msg, err := json.Marshal(obj) msg, err := json.Marshal(obj)
if err != nil { if err != nil {
c.Error(400, "Internal Server Error") c.Error(http.StatusInternalServerError, "Internal Server Error")
return
} }
c.Reply(string(msg)) c.Reply(string(msg))
} }
@ -72,11 +93,38 @@ func (c *Context) RenderEx(tmpl string, content interface{}) {
cnt := SnapContent{ cnt := SnapContent{
User: c.GetUser(), User: c.GetUser(),
Theme: c.srv.theme, Theme: c.srv.theme,
Meta: c.srv.meta,
Content: content, Content: content,
} }
c.srv.render(c.w, tmpl, &cnt) c.srv.render(c.w, tmpl, &cnt)
} }
func (c *Context) RenderWithMeta(tmpl string, meta map[string]string, content interface{}) {
// Merge metas
if meta == nil {
meta = make(map[string]string)
}
for k, v := range c.srv.meta {
if _, ok := meta[k]; !ok {
meta[k] = v
}
}
cnt := SnapContent{
User: c.GetUser(),
Theme: c.srv.theme,
Meta: meta,
Content: content,
}
if c.auth != nil {
cnt.UserProperties = c.auth.Properties
}
c.srv.render(c.w, tmpl, &cnt)
}
func (c *Context) GetVar(k string) (string, bool) { func (c *Context) GetVar(k string) (string, bool) {
v, ok := c.vars[k] v, ok := c.vars[k]
return v, ok return v, ok
@ -90,6 +138,18 @@ func (c *Context) GetVarDefault(k string, def string) string {
return v return v
} }
func (c *Context) GetVarAsSlice(k string, delim string, def []string) []string {
v, ok := c.vars[k]
if !ok {
return def
}
r := strings.Split(v, delim)
if len(r) == 1 && r[0] == "" {
return def
}
return r
}
func (c *Context) ParseForm() error { func (c *Context) ParseForm() error {
return c.r.ParseForm() return c.r.ParseForm()
} }
@ -109,6 +169,15 @@ func (c *Context) FormValueUint64(k string) uint64 {
return val return val
} }
func (c *Context) FormValueAsSlice(k string, delim string) []string {
v := c.r.FormValue(k)
r := strings.Split(v, delim)
if len(r) == 1 && r[0] == "" {
return []string{}
}
return r
}
func (c *Context) Redirect(url string) { func (c *Context) Redirect(url string) {
http.Redirect(c.w, c.r, url, http.StatusSeeOther) http.Redirect(c.w, c.r, url, http.StatusSeeOther)
} }
@ -117,6 +186,11 @@ func (c *Context) QueryValue(key string) string {
return c.r.URL.Query().Get(key) return c.r.URL.Query().Get(key)
} }
func (c *Context) QueryHas(key string) bool {
_, ok := c.r.URL.Query()[key]
return ok
}
func (c *Context) QueryValueWithDefault(key string, def string) string { func (c *Context) QueryValueWithDefault(key string, def string) string {
val := c.r.URL.Query().Get(key) val := c.r.URL.Query().Get(key)
if val == "" { if val == "" {

View File

@ -28,7 +28,9 @@ type Server struct {
router *mux.Router router *mux.Router
templates string templates string
cachedTmpl *template.Template cachedTmpl *template.Template
templateFuncs template.FuncMap
meta map[string]string
// testModeEnabled if test mode is enabled we will not render the template but jsut return the // testModeEnabled if test mode is enabled we will not render the template but jsut return the
// json object // json object
testModeEnabled bool testModeEnabled bool
@ -39,6 +41,14 @@ type SnapBaseContent struct {
Content interface{} Content interface{}
} }
func noescape(str string) template.HTML {
return template.HTML(str)
}
var builtinFuncMap = template.FuncMap{
"noescape": noescape,
}
func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http.Request) *Context { func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http.Request) *Context {
c := &Context{ c := &Context{
r: r, r: r,
@ -50,9 +60,30 @@ func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http
return c return c
} }
func (s *Server) withLoginHandler(auth auth.Authenticator, loginHandler func(c *Context) bool, handle func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if s.debug {
log.Debug("authenticated request with login ui handler: ", r.RequestURI)
}
rec, ok := auth.DoAuth(w, r)
if !ok {
log.Debug("authenticated request with login ui handler to login ", r.RequestURI)
c := s.makeContext(rec, w, r)
if loginHandler(c) {
handle(c)
}
} else {
c := s.makeContext(rec, w, r)
handle(c)
}
}
}
func (s *Server) authenticated(auth auth.Authenticator, redirect string, handle func(c *Context)) http.HandlerFunc { func (s *Server) authenticated(auth auth.Authenticator, redirect string, handle func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if s.debug {
log.Debug("authenticated request: ", r.RequestURI) log.Debug("authenticated request: ", r.RequestURI)
}
rec, ok := auth.DoAuth(w, r) rec, ok := auth.DoAuth(w, r)
if !ok { if !ok {
@ -70,7 +101,9 @@ func (s *Server) authenticated(auth auth.Authenticator, redirect string, handle
func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc { func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if s.debug {
log.Debug("request: ", r.RequestURI) log.Debug("request: ", r.RequestURI)
}
c := s.makeContext(nil, w, r) c := s.makeContext(nil, w, r)
if s.auth != nil { if s.auth != nil {
@ -119,7 +152,7 @@ func (s *Server) parseTemplates(t *template.Template, filenames ...string) (*tem
// works. Otherwise we create a new template associated with t. // works. Otherwise we create a new template associated with t.
var tmpl *template.Template var tmpl *template.Template
if t == nil { if t == nil {
t = template.New(name) t = template.New(name).Funcs(s.templateFuncs)
} }
if name == t.Name() { if name == t.Name() {
tmpl = t tmpl = t
@ -165,7 +198,7 @@ func Walk(fs http.FileSystem, base string, walkFunc func(path string, info os.Fi
} }
func (s *Server) LoadTemplatesFS(fs http.FileSystem, base string) (*template.Template, error) { func (s *Server) LoadTemplatesFS(fs http.FileSystem, base string) (*template.Template, error) {
tmpl := template.New("") tmpl := template.New("").Funcs(s.templateFuncs)
err := Walk(fs, base, func(path string, info os.FileInfo, err error) error { err := Walk(fs, base, func(path string, info os.FileInfo, err error) error {
if strings.Contains(path, ".html") { if strings.Contains(path, ".html") {
@ -236,6 +269,13 @@ func (s *Server) HandleFuncAuthenticated(path, redirect string, f func(c *Contex
return s.router.HandleFunc(path, s.authenticated(s.auth, redirect, f)) return s.router.HandleFunc(path, s.authenticated(s.auth, redirect, f))
} }
func (s *Server) HandleFuncAuthenticatedWithLogin(path string, loginHandler func(c *Context) bool, contentHandler func(c *Context)) *mux.Route {
if s.auth == nil {
return nil
}
return s.router.HandleFunc(path, s.withLoginHandler(s.auth, loginHandler, contentHandler))
}
func (s *Server) HandleFuncCustomAuth(auth auth.Authenticator, path, redirect string, f func(c *Context)) *mux.Route { func (s *Server) HandleFuncCustomAuth(auth auth.Authenticator, path, redirect string, f func(c *Context)) *mux.Route {
if auth == nil { if auth == nil {
log.Warn("Nil auth on", path) log.Warn("Nil auth on", path)
@ -318,6 +358,45 @@ func (s *Server) WithDebug(debugURL string) *Server {
return s return s
} }
func (s *Server) WithMetadata(meta map[string]string) *Server {
s.meta = meta
return s
}
func (s *Server) WithTemplateFuncs(funcs template.FuncMap) *Server {
for k, f := range funcs {
s.templateFuncs[k] = f
}
return s
}
func (s *Server) WithAuth(auth auth.Authenticator) *Server {
s.auth = auth
return s
}
func (s *Server) WithHealthCheck(version, date string, status func() (bool, string)) {
s.HandleFunc("/_health", func(c *Context) {
ok, msg := status()
hc := struct {
Version string
Date string
Status string
}{
Version: version,
Date: date,
Status: msg,
}
if ok {
c.ReplyObject(&hc)
return
}
c.ErrorObject(http.StatusServiceUnavailable, &hc)
})
}
func (s *Server) Dump() { func (s *Server) Dump() {
fmt.Printf(" Theme: %s\n", s.theme) fmt.Printf(" Theme: %s\n", s.theme)
fmt.Printf(" Templates: %s\n", s.templates) fmt.Printf(" Templates: %s\n", s.templates)
@ -330,7 +409,9 @@ func New(address string, path string, auth auth.Authenticator) *Server {
address: address, address: address,
fs: http.FileSystem(http.Dir(path)), fs: http.FileSystem(http.Dir(path)),
templates: "/templates", templates: "/templates",
templateFuncs: builtinFuncMap,
theme: "/static/css/default.css", theme: "/static/css/default.css",
meta: make(map[string]string),
} }
return &s return &s
} }

7
utility.go Normal file
View File

@ -0,0 +1,7 @@
package snap
func Redirect(to string) func(c *Context) {
return func(c *Context) {
c.Redirect(to)
}
}