summaryrefslogtreecommitdiff
path: root/srv/src
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2022-05-14 17:02:30 -0600
committerBrian Picciano <mediocregopher@gmail.com>2022-05-14 17:02:30 -0600
commite41ff2b897be24a894e75b850f1c06652cc034be (patch)
tree816cc87b789dd96b60f212311e161af1bb583e6a /srv/src
parent4c04177c05355ddb92d3d31a4c5cfbaa86555a13 (diff)
Implement index handler
This involved re-arranging how templates are being parsed, slightly.
Diffstat (limited to 'srv/src')
-rw-r--r--srv/src/api/api.go21
-rw-r--r--srv/src/api/middleware.go9
-rw-r--r--srv/src/api/render.go (renamed from srv/src/api/posts.go)74
-rw-r--r--srv/src/api/tpl/index.html20
-rw-r--r--srv/src/post/post.go5
5 files changed, 108 insertions, 21 deletions
diff --git a/srv/src/api/api.go b/srv/src/api/api.go
index 92771a1..79979be 100644
--- a/srv/src/api/api.go
+++ b/srv/src/api/api.go
@@ -3,10 +3,8 @@ package api
import (
"context"
- "embed"
"errors"
"fmt"
- "html/template"
"net"
"net/http"
"net/http/httputil"
@@ -22,11 +20,6 @@ import (
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
)
-//go:embed tpl
-var fs embed.FS
-
-var tpls = template.Must(template.ParseFS(fs, "tpl/*"))
-
// Params are used to instantiate a new API instance. All fields are required
// unless otherwise noted.
type Params struct {
@@ -184,10 +177,9 @@ func (a *api) handler() http.Handler {
)))
var apiHandler http.Handler = apiMux
- apiHandler = postOnlyMiddleware(apiHandler) // TODO probably should be last?
apiHandler = checkCSRFMiddleware(apiHandler)
- apiHandler = logMiddleware(a.params.Logger, apiHandler)
- apiHandler = annotateMiddleware(apiHandler)
+ apiHandler = postOnlyMiddleware(apiHandler)
+ apiHandler = logReqMiddleware(apiHandler)
apiHandler = addResponseHeaders(map[string]string{
"Cache-Control": "no-store, max-age=0",
"Pragma": "no-cache",
@@ -196,7 +188,12 @@ func (a *api) handler() http.Handler {
mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
- mux.Handle("/v2/posts/", a.postHandler())
+ // TODO need to setCSRFMiddleware on all these rendering endpoints
+ mux.Handle("/v2/posts/", a.renderPostHandler())
+ mux.Handle("/v2/", a.renderIndexHandler())
+
+ var globalHandler http.Handler = mux
+ globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler)
- return mux
+ return globalHandler
}
diff --git a/srv/src/api/middleware.go b/srv/src/api/middleware.go
index 0b3eec7..fcd29b3 100644
--- a/srv/src/api/middleware.go
+++ b/srv/src/api/middleware.go
@@ -19,7 +19,7 @@ func addResponseHeaders(headers map[string]string, h http.Handler) http.Handler
})
}
-func annotateMiddleware(h http.Handler) http.Handler {
+func setLoggerMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
type reqInfoKey string
@@ -34,6 +34,7 @@ func annotateMiddleware(h http.Handler) http.Handler {
)
r = r.WithContext(ctx)
+ r = apiutil.SetRequestLogger(r, logger)
h.ServeHTTP(rw, r)
})
}
@@ -58,11 +59,9 @@ func (lrw *logResponseWriter) WriteHeader(statusCode int) {
lrw.ResponseWriter.WriteHeader(statusCode)
}
-func logMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
+func logReqMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- r = apiutil.SetRequestLogger(r, logger)
-
lrw := newLogResponseWriter(rw)
started := time.Now()
@@ -77,7 +76,7 @@ func logMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
logCtxKey("response_code"), lrw.statusCode,
)
- logger.Info(ctx, "handled HTTP request")
+ apiutil.GetRequestLogger(r).Info(ctx, "handled HTTP request")
})
}
diff --git a/srv/src/api/posts.go b/srv/src/api/render.go
index cc7a176..b2ca3c8 100644
--- a/srv/src/api/posts.go
+++ b/srv/src/api/render.go
@@ -1,9 +1,11 @@
package api
import (
+ "embed"
"errors"
"fmt"
"html/template"
+ "io/fs"
"net/http"
"path/filepath"
"strings"
@@ -15,7 +17,75 @@ import (
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
)
-func (a *api) postHandler() http.Handler {
+//go:embed tpl
+var tplFS embed.FS
+
+func mustParseTpl(name string) *template.Template {
+
+ mustRead := func(fileName string) string {
+ path := filepath.Join("tpl", fileName)
+
+ b, err := fs.ReadFile(tplFS, path)
+ if err != nil {
+ panic(fmt.Errorf("reading file %q from tplFS: %w", path, err))
+ }
+
+ return string(b)
+ }
+
+ tpl := template.Must(template.New("").Parse(mustRead(name)))
+ tpl = template.Must(tpl.New("base.html").Parse(mustRead("base.html")))
+
+ return tpl
+}
+
+func (a *api) renderIndexHandler() http.Handler {
+
+ tpl := mustParseTpl("index.html")
+ const pageCount = 20
+
+ return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+
+ if path := r.URL.Path; !strings.HasSuffix(path, "/") && filepath.Base(path) != "index.html" {
+ http.Error(rw, "Page not found", 404)
+ return
+ }
+
+ page, err := apiutil.StrToInt(r.FormValue("p"), 0)
+ if err != nil {
+ apiutil.BadRequest(
+ rw, r, fmt.Errorf("invalid page number: %w", err),
+ )
+ return
+ }
+
+ posts, _, err := a.params.PostStore.Get(page, pageCount)
+ if err != nil {
+ apiutil.InternalServerError(
+ rw, r, fmt.Errorf("fetching page %d of posts: %w", page, err),
+ )
+ return
+ }
+
+ tplData := struct {
+ Posts []post.StoredPost
+ }{
+ Posts: posts,
+ }
+
+ if err := tpl.Execute(rw, tplData); err != nil {
+ apiutil.InternalServerError(
+ rw, r, fmt.Errorf("rendering index: %w", err),
+ )
+ return
+ }
+ })
+}
+
+func (a *api) renderPostHandler() http.Handler {
+
+ tpl := mustParseTpl("post.html")
+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html")
@@ -81,7 +151,7 @@ func (a *api) postHandler() http.Handler {
}
}
- if err := tpls.ExecuteTemplate(rw, "post.html", tplData); err != nil {
+ if err := tpl.Execute(rw, tplData); err != nil {
apiutil.InternalServerError(
rw, r, fmt.Errorf("rendering post with id %q: %w", id, err),
)
diff --git a/srv/src/api/tpl/index.html b/srv/src/api/tpl/index.html
new file mode 100644
index 0000000..240df92
--- /dev/null
+++ b/srv/src/api/tpl/index.html
@@ -0,0 +1,20 @@
+{{ define "body" }}
+<ul id="posts-list">
+
+ {{ range .Posts }}
+ <li>
+ <h2>
+ <a href="{{ .HTTPPath }}">{{ .Title }}</a>
+ </h2>
+ <span>{{ .PublishedAt.Format "2006-01-02" }}</span>
+ {{ if not .LastUpdatedAt.IsZero }}
+ <span>(Updated {{ .LastUpdatedAt.Format "2006-01-02" }})</span>
+ {{ end }}
+ <p>{{ .Description }}</p>
+ </li>
+ {{ end }}
+
+</ul>
+{{ end }}
+
+{{ template "base.html" . }}
diff --git a/srv/src/post/post.go b/srv/src/post/post.go
index 5835995..30ded15 100644
--- a/srv/src/post/post.go
+++ b/srv/src/post/post.go
@@ -58,8 +58,9 @@ type Store interface {
// overwrites a previous Post with the same ID, if there was one.
Set(post Post, now time.Time) error
- // Get returns count StoredPosts, sorted time descending, offset by the given page
- // number. The returned boolean indicates if there are more pages or not.
+ // Get returns count StoredPosts, sorted time descending, offset by the
+ // given page number. The returned boolean indicates if there are more pages
+ // or not.
Get(page, count int) ([]StoredPost, bool, error)
// GetByID will return the StoredPost with the given ID, or ErrPostNotFound.