diff --git a/app.go b/app.go index c21b4d7..db928e3 100644 --- a/app.go +++ b/app.go @@ -41,6 +41,7 @@ func NewDatetime() (*Datetime, error) { mux.Handle("/js/", http.FileServer(http.Dir("assets"))) mux.Handle("/css/", http.FileServer(http.Dir("assets"))) mux.Handle("/favicon.ico", http.FileServer(http.Dir("assets"))) + mux.HandleFunc("/search", app.search) mux.HandleFunc("/", app.index) return app, nil @@ -51,6 +52,11 @@ type appRequest struct { Req Request } +type appSearch struct { + App Datetime + Req map[string]string +} + // index handles all incoming page requests func (app Datetime) index(w http.ResponseWriter, req *http.Request) { var err error @@ -86,3 +92,34 @@ func (app Datetime) index(w http.ResponseWriter, req *http.Request) { return } } + +// search handles zone search queries +func (app Datetime) search(w http.ResponseWriter, req *http.Request) { + var err error + + if req.Method != http.MethodGet && req.Method != http.MethodHead { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + tmpl := app.loadTemplate("search", w, req) + if tmpl == nil { + return + } + if req.Method == http.MethodHead { + return + } + + // TODO: do search + search := map[string]string{ + "hi": "hello", + } + + l.Debug("rendering template", zap.Reflect("search", search)) + err = tmpl.Execute(w, appSearch{app, search}) + if err != nil { + l.Error("templating failed", zap.Error(err)) + app.templateError(HTTPError{http.StatusInternalServerError, err}, w, req) + return + } +} diff --git a/mime.go b/mime.go index 3741816..6302239 100644 --- a/mime.go +++ b/mime.go @@ -57,6 +57,7 @@ type responseType int const ( responsePlain responseType = iota responseHTML responseType = iota + responseJSON responseType = iota responseAny responseType = iota responseUnknown responseType = iota ) @@ -64,6 +65,7 @@ const ( const ( responsePlainMime = "text/plain" responseHTMLMime = "text/html" + responseJSONMime = "application/json" responseAnyMime = "*/*" ) @@ -77,6 +79,9 @@ func chooseResponseType(accept string) responseType { if m.media == responseHTMLMime { return responseHTML } + if m.media == responseJSONMime { + return responseJSON + } if m.media == responseAnyMime { return responseAny } diff --git a/template.go b/template.go index b08f7d3..266a914 100644 --- a/template.go +++ b/template.go @@ -1,12 +1,18 @@ package main import ( + "errors" + "fmt" "html/template" "net/http" "go.uber.org/zap" ) +// ErrTemplateNotFound is thrown when a template with the requested MIME type +// is not found. +var ErrTemplateNotFound = errors.New("unable to find template") + // loadTemplate returns a matching template for the request. It also causes an // error if the template is not found or the Accept parameters are incorrect. func (app Datetime) loadTemplate(name string, w http.ResponseWriter, req *http.Request) *template.Template { @@ -17,8 +23,10 @@ func (app Datetime) loadTemplate(name string, w http.ResponseWriter, req *http.R return nil } if tmpl == nil { - l.Error("unable to find template", zap.String("name", name), zap.String("accept", accept)) - app.simpleError(HTTPError{http.StatusInternalServerError, ErrNoTemplate}, w, req) + err := fmt.Errorf("%w \"%s\" for \"%s\"", ErrTemplateNotFound, name, accept) + l.Warn("unable to find template", zap.Error(err), zap.String("name", name), zap.String("accept", accept)) + app.simpleError(HTTPError{http.StatusNotAcceptable, err}, w, req) + //app.simpleError(HTTPError{http.StatusInternalServerError, ErrNoTemplate}, w, req) return nil } w.Header().Set("Content-Type", contentType) @@ -36,6 +44,9 @@ func (app Datetime) chooseTemplate(accept string, name string) (t *template.Temp case responseHTML: t = app.tmpl.Lookup(name + ".html") contentType = "text/html" + case responseJSON: + t = app.tmpl.Lookup(name + ".json") + contentType = "application/json" case responseAny: t = app.tmpl.Lookup(name + ".txt") contentType = "text/plain" diff --git a/templatehelpers.go b/templatehelpers.go index 4381922..4cf20a3 100644 --- a/templatehelpers.go +++ b/templatehelpers.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "html/template" "net/http" @@ -10,6 +11,8 @@ import ( var templateFuncs = map[string]interface{}{ "statusText": templateFuncStatusText, "thisIsSafe": templateFuncThisIsSafe, + // Encoding + "jsonMarshal": templateFuncJSONMarshal, // Formatting "formatOffset": templateFuncFormatOffset, // Logic @@ -23,6 +26,14 @@ func templateFuncThisIsSafe(s string) template.HTML { return template.HTML(s) } +func templateFuncJSONMarshal(v interface{}) string { + b, err := json.Marshal(v) + if err != nil { + return "" + } + return string(b) +} + func templateFuncFormatOffset(offset int) string { return FormatZoneOffset(offset) } diff --git a/templates/index.html b/templates/index.html index e8f2d5e..2421b42 100644 --- a/templates/index.html +++ b/templates/index.html @@ -53,7 +53,9 @@ {{template "interactive-icons.html"}} + diff --git a/templates/search.json b/templates/search.json new file mode 100644 index 0000000..7e4faff --- /dev/null +++ b/templates/search.json @@ -0,0 +1 @@ +{{.Req | jsonMarshal | thisIsSafe }}