Implement index handler
This involved re-arranging how templates are being parsed, slightly.
This commit is contained in:
parent
4c04177c05
commit
e41ff2b897
@ -3,10 +3,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@ -22,11 +20,6 @@ import (
|
|||||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
"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
|
// Params are used to instantiate a new API instance. All fields are required
|
||||||
// unless otherwise noted.
|
// unless otherwise noted.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
@ -184,10 +177,9 @@ func (a *api) handler() http.Handler {
|
|||||||
)))
|
)))
|
||||||
|
|
||||||
var apiHandler http.Handler = apiMux
|
var apiHandler http.Handler = apiMux
|
||||||
apiHandler = postOnlyMiddleware(apiHandler) // TODO probably should be last?
|
|
||||||
apiHandler = checkCSRFMiddleware(apiHandler)
|
apiHandler = checkCSRFMiddleware(apiHandler)
|
||||||
apiHandler = logMiddleware(a.params.Logger, apiHandler)
|
apiHandler = postOnlyMiddleware(apiHandler)
|
||||||
apiHandler = annotateMiddleware(apiHandler)
|
apiHandler = logReqMiddleware(apiHandler)
|
||||||
apiHandler = addResponseHeaders(map[string]string{
|
apiHandler = addResponseHeaders(map[string]string{
|
||||||
"Cache-Control": "no-store, max-age=0",
|
"Cache-Control": "no-store, max-age=0",
|
||||||
"Pragma": "no-cache",
|
"Pragma": "no-cache",
|
||||||
@ -196,7 +188,12 @@ func (a *api) handler() http.Handler {
|
|||||||
|
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
|
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())
|
||||||
|
|
||||||
return mux
|
var globalHandler http.Handler = mux
|
||||||
|
globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler)
|
||||||
|
|
||||||
|
return globalHandler
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
type reqInfoKey string
|
type reqInfoKey string
|
||||||
@ -34,6 +34,7 @@ func annotateMiddleware(h http.Handler) http.Handler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
r = apiutil.SetRequestLogger(r, logger)
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -58,11 +59,9 @@ func (lrw *logResponseWriter) WriteHeader(statusCode int) {
|
|||||||
lrw.ResponseWriter.WriteHeader(statusCode)
|
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) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
r = apiutil.SetRequestLogger(r, logger)
|
|
||||||
|
|
||||||
lrw := newLogResponseWriter(rw)
|
lrw := newLogResponseWriter(rw)
|
||||||
|
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
@ -77,7 +76,7 @@ func logMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
|
|||||||
logCtxKey("response_code"), lrw.statusCode,
|
logCtxKey("response_code"), lrw.statusCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.Info(ctx, "handled HTTP request")
|
apiutil.GetRequestLogger(r).Info(ctx, "handled HTTP request")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -15,7 +17,75 @@ import (
|
|||||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
"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) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html")
|
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(
|
apiutil.InternalServerError(
|
||||||
rw, r, fmt.Errorf("rendering post with id %q: %w", id, err),
|
rw, r, fmt.Errorf("rendering post with id %q: %w", id, err),
|
||||||
)
|
)
|
20
srv/src/api/tpl/index.html
Normal file
20
srv/src/api/tpl/index.html
Normal file
@ -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" . }}
|
@ -58,8 +58,9 @@ type Store interface {
|
|||||||
// overwrites a previous Post with the same ID, if there was one.
|
// overwrites a previous Post with the same ID, if there was one.
|
||||||
Set(post Post, now time.Time) error
|
Set(post Post, now time.Time) error
|
||||||
|
|
||||||
// Get returns count StoredPosts, sorted time descending, offset by the given page
|
// Get returns count StoredPosts, sorted time descending, offset by the
|
||||||
// number. The returned boolean indicates if there are more pages or not.
|
// given page number. The returned boolean indicates if there are more pages
|
||||||
|
// or not.
|
||||||
Get(page, count int) ([]StoredPost, bool, error)
|
Get(page, count int) ([]StoredPost, bool, error)
|
||||||
|
|
||||||
// GetByID will return the StoredPost with the given ID, or ErrPostNotFound.
|
// GetByID will return the StoredPost with the given ID, or ErrPostNotFound.
|
||||||
|
Loading…
Reference in New Issue
Block a user