From 10c60b3c9ba2c17419534cf4089328a66568e4f1 Mon Sep 17 00:00:00 2001 From: Mitchell Riedstra Date: Sat, 3 Dec 2022 13:38:05 -0500 Subject: Add a couple handlers to serve up JSON and markdown --- page/page.go | 14 +++++- page/renderJson.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ page/renderMarkdown.go | 69 +++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 page/renderJson.go create mode 100644 page/renderMarkdown.go (limited to 'page') diff --git a/page/page.go b/page/page.go index d34a1f7..29b35bc 100644 --- a/page/page.go +++ b/page/page.go @@ -55,7 +55,10 @@ type Page struct { Date *PageTime Published bool Vars map[string]interface{} - markdown []byte + // Keys of vars to be included when RenderJson is called, all vars are + // omitted if empty. + JsonVars []string + markdown []byte } // Global is meant to be supplied by external users of this package to populate @@ -232,6 +235,13 @@ func (p *Page) Read() error { // markdown file, then runs it through the markdown parser. Typically // this is called in the base template. func (p *Page) RenderBody() (string, error) { + s, err := p.GetMarkdown() + return string(blackfriday.Run([]byte(s))), err +} + +// GetMarkdown renders and executes a template from the body of the +// markdown file, then simply returns the unrendered markdown. +func (p *Page) GetMarkdown() (string, error) { buf := &bytes.Buffer{} t, err := template.New("Body").Funcs(Funcs).Parse(string(p.markdown)) @@ -245,7 +255,7 @@ func (p *Page) RenderBody() (string, error) { return "", fmt.Errorf("template execute; %w", err) } - return string(blackfriday.Run(buf.Bytes())), nil + return buf.String(), nil } func (p Page) String() string { diff --git a/page/renderJson.go b/page/renderJson.go new file mode 100644 index 0000000..52e707d --- /dev/null +++ b/page/renderJson.go @@ -0,0 +1,118 @@ +package page + +import ( + "bytes" + "encoding/json" + "errors" + "io/fs" + "net/http" + "path/filepath" +) + +// RenderJson is analogous to Render, though it renders the page, +// as a JSON key "content", optionally from the YAML it'll include +// additional keys in the json if specified under the 'pageJson' key. +// E.g. +// --- +// title: Some page +// description: Some page desc +// jsonvars: +// - includeMeInJSON +// vars: +// notIncluded: some value +// includeMeInJSON: +// some: arbitrary +// data: to +// send: down +// |-- +// My page content, wee! +func RenderJson(w http.ResponseWriter, r *http.Request, + path string, vars map[string]interface{}, statusCode int) { + u := getURLPath(r) + + u = filepath.Join(".", u) + + // Sepcifically use the specified path for the page + p := NewPage(path) + + if vars != nil { + p.Vars = vars + } + + out := map[string]interface{}{} + + buf := &bytes.Buffer{} + err := p.Render(buf) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + renderJsonErr(w, r, "Not found", http.StatusNotFound) + + return + } + + Logger.Printf("%s %s path: %s rendering encountered: %s", + r.RemoteAddr, + r.Method, + u, + err) + renderJsonErr(w, r, "Internal server error", + http.StatusInternalServerError) + + return + } + + if r.URL.Query().Get("content") == "1" { + out["Content"] = buf.String() + } + + if r.URL.Query().Get("markdown") == "1" { + // Tossing the error, since it would've been revealed above + md, _ := p.GetMarkdown() + out["Markdown"] = md + } + + // Make a "set" of keys + keys := map[string]struct{}{} + for _, k := range p.JsonVars { + keys[k] = struct{}{} + } + + // And chuck the keys specified into the output + for k, v := range p.Vars { + _, ok := keys[k] + if ok { + out[k] = v + } + } + + w.WriteHeader(statusCode) + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + enc.SetEscapeHTML(false) + + err = enc.Encode(out) + if err != nil { + Logger.Printf("%s %s %d %s: while writing buf: %s", + r.RemoteAddr, + r.Method, + statusCode, + u, + err) + + return + } + + Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u) +} + +func renderJsonErr(w http.ResponseWriter, r *http.Request, msg string, + statusCode int) { + enc := json.NewEncoder(w) + w.WriteHeader(statusCode) + _ = enc.Encode(&struct { + Status string + }{ + Status: msg, + }) +} diff --git a/page/renderMarkdown.go b/page/renderMarkdown.go new file mode 100644 index 0000000..c876169 --- /dev/null +++ b/page/renderMarkdown.go @@ -0,0 +1,69 @@ +package page + +import ( + "bytes" + "errors" + "io/fs" + "net/http" + "path/filepath" +) + +// RenderMarkdown is analogous to Render, except it spits out rendered markdown +// as text/plain +func RenderMarkdown(w http.ResponseWriter, r *http.Request, + path string, vars map[string]interface{}, statusCode int) { + u := getURLPath(r) + + u = filepath.Join(".", u) + + // Sepcifically use the specified path for the page + p := NewPage(path) + + if vars != nil { + p.Vars = vars + } + + buf := &bytes.Buffer{} + + err := p.Render(buf) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + w.WriteHeader(http.StatusNotFound) + w.Header().Set("Content-type", "text/plain") + w.Write([]byte("Not found")) + + return + } + + Logger.Printf("%s %s path: %s rendering encountered: %s", + r.RemoteAddr, + r.Method, + u, + err) + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("Content-type", "text/plain") + w.Write([]byte("Internal server error")) + + return + } + + // Error was handled above + md, _ := p.GetMarkdown() + + w.WriteHeader(statusCode) + w.Header().Set("Content-type", "text/plain") + + _, err = w.Write([]byte(md)) + if err != nil { + Logger.Printf("%s %s %d %s: while writing buf: %s", + r.RemoteAddr, + r.Method, + statusCode, + u, + err) + + return + } + + Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u) +} -- cgit v1.2.3