From 1f3ae665ed2e58ca572678ce7caf8b711f226392 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 29 Nov 2022 20:59:31 +0100 Subject: [PATCH] Introduce EDIT and MANAGE methods All admin "index" pages are moved under MANAGE, so that we can have (for example) and normal "GET /posts" page later which would replace the current index page, and potentially corresponding pages for the other categories. The EDIT method replaces the old `?edit` pattern, which normalizes how we differentiate page functionality generally. --- src/http/api.go | 72 +++++++++++-------- src/http/apiutil/apiutil.go | 5 +- src/http/assets.go | 15 ++-- src/http/drafts.go | 63 +--------------- src/http/posts.go | 50 +++++-------- src/http/tpl.go | 24 +++++-- src/http/tpl/admin.html | 6 +- ...aft-posts.html => draft-posts-manage.html} | 6 +- .../{assets.html => post-assets-manage.html} | 0 .../tpl/{edit-post.html => post-edit.html} | 4 +- .../tpl/{posts.html => posts-manage.html} | 2 +- 11 files changed, 97 insertions(+), 150 deletions(-) rename src/http/tpl/{draft-posts.html => draft-posts-manage.html} (82%) rename src/http/tpl/{assets.html => post-assets-manage.html} (100%) rename src/http/tpl/{edit-post.html => post-edit.html} (96%) rename src/http/tpl/{posts.html => posts-manage.html} (94%) diff --git a/src/http/api.go b/src/http/api.go index 01cad50..cbcd182 100644 --- a/src/http/api.go +++ b/src/http/api.go @@ -190,7 +190,9 @@ func (a *api) blogHandler() http.Handler { mux.Handle("/posts/", http.StripPrefix("/posts", apiutil.MethodMux(map[string]http.Handler{ - "GET": a.renderPostHandler(), + "GET": a.getPostHandler(), + "EDIT": a.editPostHandler(false), + "MANAGE": a.managePostsHandler(), "POST": a.postPostHandler(), "DELETE": a.deletePostHandler(false), "PREVIEW": a.previewPostHandler(), @@ -200,6 +202,7 @@ func (a *api) blogHandler() http.Handler { mux.Handle("/assets/", http.StripPrefix("/assets", apiutil.MethodMux(map[string]http.Handler{ "GET": a.getPostAssetHandler(), + "MANAGE": a.managePostAssetsHandler(), "POST": a.postPostAssetHandler(), "DELETE": a.deletePostAssetHandler(), }), @@ -211,7 +214,8 @@ func (a *api) blogHandler() http.Handler { authMiddleware(a.auther)( apiutil.MethodMux(map[string]http.Handler{ - "GET": a.renderDraftPostHandler(), + "EDIT": a.editPostHandler(true), + "MANAGE": a.manageDraftPostsHandler(), "POST": a.postDraftPostHandler(), "DELETE": a.deletePostHandler(true), "PREVIEW": a.previewPostHandler(), @@ -227,17 +231,21 @@ func (a *api) blogHandler() http.Handler { mux.Handle("/feed.xml", a.renderFeedHandler()) mux.Handle("/", a.renderIndexHandler()) + readOnlyMiddlewares := []middleware{ + logReqMiddleware, // only log GETs on cache miss + cacheMiddleware(cache), + } + + readWriteMiddlewares := []middleware{ + purgeCacheOnOKMiddleware(cache), + authMiddleware(a.auther), + } + h := apiutil.MethodMux(map[string]http.Handler{ - "GET": applyMiddlewares( - mux, - logReqMiddleware, // only log GETs on cache miss - cacheMiddleware(cache), - ), - "*": applyMiddlewares( - mux, - purgeCacheOnOKMiddleware(cache), - authMiddleware(a.auther), - ), + "GET": applyMiddlewares(mux, readOnlyMiddlewares...), + "MANAGE": applyMiddlewares(mux, readOnlyMiddlewares...), + "EDIT": applyMiddlewares(mux, readOnlyMiddlewares...), + "*": applyMiddlewares(mux, readWriteMiddlewares...), }) return h @@ -247,26 +255,30 @@ func (a *api) handler() http.Handler { mux := http.NewServeMux() - mux.Handle("/api/", http.StripPrefix("/api", a.apiHandler())) - mux.Handle("/", a.blogHandler()) + mux.Handle("/api/", applyMiddlewares( + http.StripPrefix("/api", a.apiHandler()), + logReqMiddleware, + )) - h := apiutil.MethodMux(map[string]http.Handler{ - "GET": applyMiddlewares( - mux, - ), - "*": applyMiddlewares( - mux, - a.checkCSRFMiddleware, - addResponseHeadersMiddleware(map[string]string{ - "Cache-Control": "no-store, max-age=0", - "Pragma": "no-cache", - "Expires": "0", - }), - logReqMiddleware, - ), - }) + mux.Handle("/", a.blogHandler()) - h = setLoggerMiddleware(a.params.Logger)(h) + h := applyMiddlewares( + apiutil.MethodMux(map[string]http.Handler{ + "GET": applyMiddlewares( + mux, + ), + "*": applyMiddlewares( + mux, + a.checkCSRFMiddleware, + addResponseHeadersMiddleware(map[string]string{ + "Cache-Control": "no-store, max-age=0", + "Pragma": "no-cache", + "Expires": "0", + }), + ), + }), + setLoggerMiddleware(a.params.Logger), + ) return h } diff --git a/src/http/apiutil/apiutil.go b/src/http/apiutil/apiutil.go index fed6fb5..1fbadea 100644 --- a/src/http/apiutil/apiutil.go +++ b/src/http/apiutil/apiutil.go @@ -120,6 +120,9 @@ func RandStr(numBytes int) string { // // If the method "*" is defined then all methods not defined will be directed to // that handler, and 405 Method Not Allowed is never returned. +// +// If the GET argument 'method' is present then the ToUpper of that is taken to +// be the name of the method. func MethodMux(handlers map[string]http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -127,7 +130,7 @@ func MethodMux(handlers map[string]http.Handler) http.Handler { method := strings.ToUpper(r.Method) formMethod := strings.ToUpper(r.FormValue("method")) - if method == "POST" && formMethod != "" { + if formMethod != "" { method = formMethod } diff --git a/src/http/assets.go b/src/http/assets.go index 260b786..9c6c67e 100644 --- a/src/http/assets.go +++ b/src/http/assets.go @@ -59,9 +59,9 @@ func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error { } } -func (a *api) renderPostAssetsIndexHandler() http.Handler { +func (a *api) managePostAssetsHandler() http.Handler { - tpl := a.mustParseBasedTpl("assets.html") + tpl := a.mustParseBasedTpl("post-assets-manage.html") return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -86,17 +86,10 @@ func (a *api) renderPostAssetsIndexHandler() http.Handler { func (a *api) getPostAssetHandler() http.Handler { - renderIndexHandler := a.renderPostAssetsIndexHandler() - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { id := filepath.Base(r.URL.Path) - if id == "/" { - renderIndexHandler.ServeHTTP(rw, r) - return - } - maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0) if err != nil { apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err)) @@ -172,7 +165,7 @@ func (a *api) postPostAssetHandler() http.Handler { return } - a.executeRedirectTpl(rw, r, a.assetsURL(false)) + a.executeRedirectTpl(rw, r, a.manageAssetsURL(false)) }) } @@ -199,6 +192,6 @@ func (a *api) deletePostAssetHandler() http.Handler { return } - a.executeRedirectTpl(rw, r, a.assetsURL(false)) + a.executeRedirectTpl(rw, r, a.manageAssetsURL(false)) }) } diff --git a/src/http/drafts.go b/src/http/drafts.go index cb776b0..4b98bc8 100644 --- a/src/http/drafts.go +++ b/src/http/drafts.go @@ -1,77 +1,20 @@ package http import ( - "errors" "fmt" "net/http" - "path/filepath" - "strings" "github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil" "github.com/mediocregopher/blog.mediocregopher.com/srv/post" ) -func (a *api) renderDraftPostHandler() http.Handler { +func (a *api) manageDraftPostsHandler() http.Handler { - tpl := a.mustParseBasedTpl("post.html") - renderDraftPostsIndexHandler := a.renderDraftPostsIndexHandler() - renderDraftEditPostHandler := a.renderEditPostHandler(true) - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html") - - if id == "/" { - renderDraftPostsIndexHandler.ServeHTTP(rw, r) - return - } - - if _, ok := r.URL.Query()["edit"]; ok { - renderDraftEditPostHandler.ServeHTTP(rw, r) - return - } - - p, err := a.params.PostDraftStore.GetByID(id) - - if errors.Is(err, post.ErrPostNotFound) { - http.Error(rw, "Post not found", 404) - return - } else if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("fetching post with id %q: %w", id, err), - ) - return - } - - tplPayload, err := a.postToPostTplPayload(post.StoredPost{Post: p}) - - if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf( - "generating template payload for post with id %q: %w", - id, err, - ), - ) - return - } - - executeTemplate(rw, r, tpl, tplPayload) - }) -} - -func (a *api) renderDraftPostsIndexHandler() http.Handler { - - renderEditPostHandler := a.renderEditPostHandler(true) - tpl := a.mustParseBasedTpl("draft-posts.html") + tpl := a.mustParseBasedTpl("draft-posts-manage.html") const pageCount = 20 return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if _, ok := r.URL.Query()["edit"]; ok { - renderEditPostHandler.ServeHTTP(rw, r) - return - } - page, err := apiutil.StrToInt(r.FormValue("p"), 0) if err != nil { apiutil.BadRequest( @@ -125,6 +68,6 @@ func (a *api) postDraftPostHandler() http.Handler { return } - a.executeRedirectTpl(rw, r, a.draftURL(p.ID, false)+"?edit") + a.executeRedirectTpl(rw, r, a.editDraftPostURL(p.ID, false)) }) } diff --git a/src/http/posts.go b/src/http/posts.go index 09daac4..1950113 100644 --- a/src/http/posts.go +++ b/src/http/posts.go @@ -123,26 +123,14 @@ func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, return tplPayload, nil } -func (a *api) renderPostHandler() http.Handler { +func (a *api) getPostHandler() http.Handler { tpl := a.mustParseBasedTpl("post.html") - renderPostsIndexHandler := a.renderPostsIndexHandler() - renderEditPostHandler := a.renderEditPostHandler(false) return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html") - if id == "/" { - renderPostsIndexHandler.ServeHTTP(rw, r) - return - } - - if _, ok := r.URL.Query()["edit"]; ok { - renderEditPostHandler.ServeHTTP(rw, r) - return - } - storedPost, err := a.params.PostStore.GetByID(id) if errors.Is(err, post.ErrPostNotFound) { @@ -171,19 +159,13 @@ func (a *api) renderPostHandler() http.Handler { }) } -func (a *api) renderPostsIndexHandler() http.Handler { +func (a *api) managePostsHandler() http.Handler { - renderEditPostHandler := a.renderEditPostHandler(false) - tpl := a.mustParseBasedTpl("posts.html") + tpl := a.mustParseBasedTpl("posts-manage.html") const pageCount = 20 return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if _, ok := r.URL.Query()["edit"]; ok { - renderEditPostHandler.ServeHTTP(rw, r) - return - } - page, err := apiutil.StrToInt(r.FormValue("p"), 0) if err != nil { apiutil.BadRequest( @@ -221,20 +203,26 @@ func (a *api) renderPostsIndexHandler() http.Handler { }) } -func (a *api) renderEditPostHandler(isDraft bool) http.Handler { +func (a *api) editPostHandler(isDraft bool) http.Handler { - tpl := a.mustParseBasedTpl("edit-post.html") + tpl := a.mustParseBasedTpl("post-edit.html") return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { id := filepath.Base(r.URL.Path) - var storedPost post.StoredPost + if id == "/" && !isDraft { + http.Error(rw, "Post id required", 400) + return + } + + var ( + storedPost post.StoredPost + err error + ) if id != "/" { - var err error - if isDraft { storedPost.Post, err = a.params.PostDraftStore.GetByID(id) } else { @@ -250,10 +238,6 @@ func (a *api) renderEditPostHandler(isDraft bool) http.Handler { ) return } - - } else if !isDraft { - http.Error(rw, "Post ID required in URL", 400) - return } tags, err := a.params.PostStore.GetTags() @@ -348,7 +332,7 @@ func (a *api) postPostHandler() http.Handler { return } - a.executeRedirectTpl(rw, r, a.postURL(p.ID, false)) + a.executeRedirectTpl(rw, r, a.editPostURL(p.ID, false)) }) } @@ -382,9 +366,9 @@ func (a *api) deletePostHandler(isDraft bool) http.Handler { } if isDraft { - a.executeRedirectTpl(rw, r, a.draftsURL(false)) + a.executeRedirectTpl(rw, r, a.manageDraftPostsURL(false)) } else { - a.executeRedirectTpl(rw, r, a.postsURL(false)) + a.executeRedirectTpl(rw, r, a.managePostsURL(false)) } }) } diff --git a/src/http/tpl.go b/src/http/tpl.go index 3e1a2ba..a9f89d7 100644 --- a/src/http/tpl.go +++ b/src/http/tpl.go @@ -49,19 +49,31 @@ func (a *api) postURL(id string, abs bool) string { return a.blogURL(path, abs) } -func (a *api) postsURL(abs bool) string { - return a.blogURL("posts", abs) +func (a *api) editPostURL(id string, abs bool) string { + return a.postURL(id, abs) + "?method=edit" } -func (a *api) assetsURL(abs bool) string { - return a.blogURL("assets", abs) +func (a *api) managePostsURL(abs bool) string { + return a.blogURL("posts?method=manage", abs) } -func (a *api) draftURL(id string, abs bool) string { +func (a *api) manageAssetsURL(abs bool) string { + return a.blogURL("assets?method=manage", abs) +} + +func (a *api) draftPostURL(id string, abs bool) string { path := filepath.Join("drafts", id) return a.blogURL(path, abs) } +func (a *api) editDraftPostURL(id string, abs bool) string { + return a.draftPostURL(id, abs) + "?method=edit" +} + +func (a *api) manageDraftPostsURL(abs bool) string { + return a.blogURL("drafts", abs) + "?method=manage" +} + func (a *api) draftsURL(abs bool) string { return a.blogURL("drafts", abs) } @@ -88,7 +100,7 @@ func (a *api) tplFuncs() template.FuncMap { return a.blogURL(path, false) }, "DraftURL": func(id string) string { - return a.draftURL(id, false) + return a.draftPostURL(id, false) }, "DateTimeFormat": func(t time.Time) string { return t.Format("2006-01-02") diff --git a/src/http/tpl/admin.html b/src/http/tpl/admin.html index f2ba4d6..510d705 100644 --- a/src/http/tpl/admin.html +++ b/src/http/tpl/admin.html @@ -7,9 +7,9 @@ mostly left open to inspection, but you will not able to change anything without providing credentials. {{ end }} diff --git a/src/http/tpl/draft-posts.html b/src/http/tpl/draft-posts-manage.html similarity index 82% rename from src/http/tpl/draft-posts.html rename to src/http/tpl/draft-posts-manage.html index 53261b9..12aadb2 100644 --- a/src/http/tpl/draft-posts.html +++ b/src/http/tpl/draft-posts-manage.html @@ -7,7 +7,7 @@

Drafts

- New Draft + New Draft

{{ if ge .Payload.PrevPage 0 }} @@ -20,9 +20,9 @@ {{ range .Payload.Posts }} - {{ .Title }} + {{ .Title }} - + Edit diff --git a/src/http/tpl/assets.html b/src/http/tpl/post-assets-manage.html similarity index 100% rename from src/http/tpl/assets.html rename to src/http/tpl/post-assets-manage.html diff --git a/src/http/tpl/edit-post.html b/src/http/tpl/post-edit.html similarity index 96% rename from src/http/tpl/edit-post.html rename to src/http/tpl/post-edit.html index f8e2730..2813754 100644 --- a/src/http/tpl/edit-post.html +++ b/src/http/tpl/post-edit.html @@ -2,11 +2,11 @@

{{ if .Payload.IsDraft }} - + Back to Drafts {{ else }} - + Back to Posts {{ end }} diff --git a/src/http/tpl/posts.html b/src/http/tpl/posts-manage.html similarity index 94% rename from src/http/tpl/posts.html rename to src/http/tpl/posts-manage.html index fbeaa41..cfb2ec9 100644 --- a/src/http/tpl/posts.html +++ b/src/http/tpl/posts-manage.html @@ -19,7 +19,7 @@ {{ .PublishedAt.Local.Format "2006-01-02 15:04:05 MST" }} {{ .Title }} - + Edit