From 1f3ae665ed2e58ca572678ce7caf8b711f226392 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 29 Nov 2022 20:59:31 +0100 Subject: 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 +- src/http/tpl/assets.html | 57 -------------- src/http/tpl/draft-posts-manage.html | 50 ++++++++++++ src/http/tpl/draft-posts.html | 50 ------------ src/http/tpl/edit-post.html | 142 ----------------------------------- src/http/tpl/post-assets-manage.html | 57 ++++++++++++++ src/http/tpl/post-edit.html | 142 +++++++++++++++++++++++++++++++++++ src/http/tpl/posts-manage.html | 47 ++++++++++++ src/http/tpl/posts.html | 47 ------------ 15 files changed, 387 insertions(+), 440 deletions(-) delete mode 100644 src/http/tpl/assets.html create mode 100644 src/http/tpl/draft-posts-manage.html delete mode 100644 src/http/tpl/draft-posts.html delete mode 100644 src/http/tpl/edit-post.html create mode 100644 src/http/tpl/post-assets-manage.html create mode 100644 src/http/tpl/post-edit.html create mode 100644 src/http/tpl/posts-manage.html delete mode 100644 src/http/tpl/posts.html (limited to 'src') 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/assets.html b/src/http/tpl/assets.html deleted file mode 100644 index f21717a..0000000 --- a/src/http/tpl/assets.html +++ /dev/null @@ -1,57 +0,0 @@ -{{ define "body" }} - -

- Back to Admin -

- -

Assets

- -

Upload Asset

- -

- If the given ID is the same as an existing asset's ID, then that asset will be - overwritten. -

- -
-
-
- -
-
-
-
-
- -
-
-
- -{{ if gt (len .Payload.IDs) 0 }} - -

Existing Assets

- - - - {{ range .Payload.IDs }} - - - - - {{ end }} - -
{{ . }} -
- -
-
- -{{ end }} - -{{ end }} - -{{ template "base.html" . }} diff --git a/src/http/tpl/draft-posts-manage.html b/src/http/tpl/draft-posts-manage.html new file mode 100644 index 0000000..12aadb2 --- /dev/null +++ b/src/http/tpl/draft-posts-manage.html @@ -0,0 +1,50 @@ +{{ define "body" }} + +

+ Back to Admin +

+ +

Drafts

+ +

+ New Draft +

+ + {{ if ge .Payload.PrevPage 0 }} +

+ < < Previous Page +

+ {{ end }} + + + + {{ range .Payload.Posts }} + + + + + + {{ end }} + +
{{ .Title }} + + Edit + + +
+ +
+
+ + {{ if ge .Payload.NextPage 0 }} +

+ Next Page > > +

+ {{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/draft-posts.html b/src/http/tpl/draft-posts.html deleted file mode 100644 index 53261b9..0000000 --- a/src/http/tpl/draft-posts.html +++ /dev/null @@ -1,50 +0,0 @@ -{{ define "body" }} - -

- Back to Admin -

- -

Drafts

- -

- New Draft -

- - {{ if ge .Payload.PrevPage 0 }} -

- < < Previous Page -

- {{ end }} - - - - {{ range .Payload.Posts }} - - - - - - {{ end }} - -
{{ .Title }} - - Edit - - -
- -
-
- - {{ if ge .Payload.NextPage 0 }} -

- Next Page > > -

- {{ end }} - -{{ end }} - -{{ template "base.html" . }} diff --git a/src/http/tpl/edit-post.html b/src/http/tpl/edit-post.html deleted file mode 100644 index f8e2730..0000000 --- a/src/http/tpl/edit-post.html +++ /dev/null @@ -1,142 +0,0 @@ -{{ define "body" }} - -

- {{ if .Payload.IsDraft }} - - Back to Drafts - - {{ else }} - - Back to Posts - - {{ end }} -

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Unique ID - - {{ if eq .Payload.Post.ID "" }} - - {{ else if .Payload.IsDraft }} - {{ .Payload.Post.ID }} - - {{ else }} - {{ .Payload.Post.ID }} - - {{ end }} -
Tags (space separated) - - - {{ if gt (len .Payload.Tags) 0 }} - - Existing tags: - {{ range $i, $tag := .Payload.Tags }} - {{ if ne $i 0 }} {{ end }}{{ $tag }} - {{ end }} - - {{ end }} -
Series - -
Title - -
Description - -
- -

- -

- -

- - - - {{ if .Payload.IsDraft }} - - - - - - - - - {{ else }} - - {{ end }} - -

- -
- -{{ end }} - -{{ template "base.html" . }} diff --git a/src/http/tpl/post-assets-manage.html b/src/http/tpl/post-assets-manage.html new file mode 100644 index 0000000..f21717a --- /dev/null +++ b/src/http/tpl/post-assets-manage.html @@ -0,0 +1,57 @@ +{{ define "body" }} + +

+ Back to Admin +

+ +

Assets

+ +

Upload Asset

+ +

+ If the given ID is the same as an existing asset's ID, then that asset will be + overwritten. +

+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +{{ if gt (len .Payload.IDs) 0 }} + +

Existing Assets

+ + + + {{ range .Payload.IDs }} + + + + + {{ end }} + +
{{ . }} +
+ +
+
+ +{{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/post-edit.html b/src/http/tpl/post-edit.html new file mode 100644 index 0000000..2813754 --- /dev/null +++ b/src/http/tpl/post-edit.html @@ -0,0 +1,142 @@ +{{ define "body" }} + +

+ {{ if .Payload.IsDraft }} + + Back to Drafts + + {{ else }} + + Back to Posts + + {{ end }} +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Unique ID + + {{ if eq .Payload.Post.ID "" }} + + {{ else if .Payload.IsDraft }} + {{ .Payload.Post.ID }} + + {{ else }} + {{ .Payload.Post.ID }} + + {{ end }} +
Tags (space separated) + + + {{ if gt (len .Payload.Tags) 0 }} + + Existing tags: + {{ range $i, $tag := .Payload.Tags }} + {{ if ne $i 0 }} {{ end }}{{ $tag }} + {{ end }} + + {{ end }} +
Series + +
Title + +
Description + +
+ +

+ +

+ +

+ + + + {{ if .Payload.IsDraft }} + + + + + + + + + {{ else }} + + {{ end }} + +

+ +
+ +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/posts-manage.html b/src/http/tpl/posts-manage.html new file mode 100644 index 0000000..cfb2ec9 --- /dev/null +++ b/src/http/tpl/posts-manage.html @@ -0,0 +1,47 @@ +{{ define "body" }} + +

+ Back to Admin +

+ +

Posts

+ + {{ if ge .Payload.PrevPage 0 }} +

+ < < Previous Page +

+ {{ end }} + + + + {{ range .Payload.Posts }} + + + + + + + {{ end }} + +
{{ .PublishedAt.Local.Format "2006-01-02 15:04:05 MST" }}{{ .Title }} + + Edit + + +
+ +
+
+ + {{ if ge .Payload.NextPage 0 }} +

+ Next Page > > +

+ {{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/posts.html b/src/http/tpl/posts.html deleted file mode 100644 index fbeaa41..0000000 --- a/src/http/tpl/posts.html +++ /dev/null @@ -1,47 +0,0 @@ -{{ define "body" }} - -

- Back to Admin -

- -

Posts

- - {{ if ge .Payload.PrevPage 0 }} -

- < < Previous Page -

- {{ end }} - - - - {{ range .Payload.Posts }} - - - - - - - {{ end }} - -
{{ .PublishedAt.Local.Format "2006-01-02 15:04:05 MST" }}{{ .Title }} - - Edit - - -
- -
-
- - {{ if ge .Payload.NextPage 0 }} -

- Next Page > > -

- {{ end }} - -{{ end }} - -{{ template "base.html" . }} -- cgit v1.2.3