drafts functionality added, needs a publish button still

main
Brian Picciano 2 years ago
parent dfa9bcb9e2
commit c3135306b3
  1. 2
      srv/src/cmd/mediocre-blog/main.go
  2. 19
      srv/src/http/api.go
  3. 110
      srv/src/http/drafts.go
  4. 48
      srv/src/http/posts.go
  5. 16
      srv/src/http/tpl.go
  6. 1
      srv/src/http/tpl/admin.html
  7. 48
      srv/src/http/tpl/draft-posts.html
  8. 10
      srv/src/http/tpl/edit-post.html
  9. 1
      srv/src/post/draft_post.go

@ -121,11 +121,13 @@ func main() {
postStore := post.NewStore(postSQLDB)
postAssetStore := post.NewAssetStore(postSQLDB)
postDraftStore := post.NewDraftStore(postSQLDB)
httpParams.Logger = logger.WithNamespace("http")
httpParams.PowManager = powMgr
httpParams.PostStore = postStore
httpParams.PostAssetStore = postAssetStore
httpParams.PostDraftStore = postDraftStore
httpParams.MailingList = ml
httpParams.GlobalRoom = chatGlobalRoom
httpParams.UserIDCalculator = chatUserIDCalc

@ -37,6 +37,7 @@ type Params struct {
PostStore post.Store
PostAssetStore post.AssetStore
PostDraftStore post.DraftStore
MailingList mailinglist.MailingList
@ -201,8 +202,8 @@ func (a *api) blogHandler() http.Handler {
mux.Handle("/posts/", http.StripPrefix("/posts",
apiutil.MethodMux(map[string]http.Handler{
"GET": a.renderPostHandler(),
"POST": a.postPostHandler(),
"DELETE": a.deletePostHandler(),
"POST": a.postPostHandler(false),
"DELETE": a.deletePostHandler(false),
"PREVIEW": a.previewPostHandler(),
}),
))
@ -215,6 +216,20 @@ func (a *api) blogHandler() http.Handler {
}),
))
mux.Handle("/drafts/", http.StripPrefix("/drafts",
// everything to do with drafts is protected
authMiddleware(a.auther)(
apiutil.MethodMux(map[string]http.Handler{
"GET": a.renderDraftPostHandler(),
"POST": a.postPostHandler(true),
"DELETE": a.deletePostHandler(true),
"PREVIEW": a.previewPostHandler(),
}),
),
))
mux.Handle("/static/", http.FileServer(http.FS(staticFS)))
mux.Handle("/follow", a.renderDumbTplHandler("follow.html"))
mux.Handle("/admin", a.renderDumbTplHandler("admin.html"))

@ -0,0 +1,110 @@
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 {
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")
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(
rw, r, fmt.Errorf("invalid page number: %w", err),
)
return
}
posts, hasMore, err := a.params.PostDraftStore.Get(page, pageCount)
if err != nil {
apiutil.InternalServerError(
rw, r, fmt.Errorf("fetching page %d of posts: %w", page, err),
)
return
}
tplPayload := struct {
Posts []post.Post
PrevPage, NextPage int
}{
Posts: posts,
PrevPage: -1,
NextPage: -1,
}
if page > 0 {
tplPayload.PrevPage = page - 1
}
if hasMore {
tplPayload.NextPage = page + 1
}
executeTemplate(rw, r, tpl, tplPayload)
})
}

@ -18,7 +18,7 @@ import (
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
)
func (a *api) parsePostBody(storedPost post.StoredPost) (*txttpl.Template, error) {
func (a *api) parsePostBody(post post.Post) (*txttpl.Template, error) {
tpl := txttpl.New("root")
tpl = tpl.Funcs(txttpl.FuncMap(a.tplFuncs()))
@ -43,7 +43,7 @@ func (a *api) parsePostBody(storedPost post.StoredPost) (*txttpl.Template, error
},
})
tpl, err := tpl.New(storedPost.ID + "-body.html").Parse(storedPost.Body)
tpl, err := tpl.New(post.ID + "-body.html").Parse(post.Body)
if err != nil {
return nil, err
@ -60,7 +60,7 @@ type postTplPayload struct {
func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) {
bodyTpl, err := a.parsePostBody(storedPost)
bodyTpl, err := a.parsePostBody(storedPost.Post)
if err != nil {
return postTplPayload{}, fmt.Errorf("parsing post body as template: %w", err)
}
@ -232,7 +232,12 @@ func (a *api) renderEditPostHandler(isDraft bool) http.Handler {
if id != "/" {
var err error
storedPost, err = a.params.PostStore.GetByID(id)
if isDraft {
storedPost.Post, err = a.params.PostDraftStore.GetByID(id)
} else {
storedPost, err = a.params.PostStore.GetByID(id)
}
if errors.Is(err, post.ErrPostNotFound) {
http.Error(rw, "Post not found", 404)
@ -291,7 +296,7 @@ func postFromPostReq(r *http.Request) (post.Post, error) {
return p, nil
}
func (a *api) postPostHandler() http.Handler {
func (a *api) postPostHandler(isDraft bool) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@ -301,7 +306,13 @@ func (a *api) postPostHandler() http.Handler {
return
}
first, err := a.params.PostStore.Set(p, time.Now())
var first bool
if isDraft {
err = a.params.PostDraftStore.Set(p)
} else {
first, err = a.params.PostStore.Set(p, time.Now())
}
if err != nil {
apiutil.InternalServerError(
@ -310,7 +321,7 @@ func (a *api) postPostHandler() http.Handler {
return
}
if first {
if !isDraft && first {
a.params.Logger.Info(r.Context(), "publishing blog post to mailing list")
urlStr := a.postURL(p.ID, true)
@ -323,11 +334,15 @@ func (a *api) postPostHandler() http.Handler {
}
}
a.executeRedirectTpl(rw, r, a.postURL(p.ID, false)+"?edit")
if isDraft {
a.executeRedirectTpl(rw, r, a.draftURL(p.ID, false)+"?edit")
} else {
a.executeRedirectTpl(rw, r, a.postURL(p.ID, false)+"?edit")
}
})
}
func (a *api) deletePostHandler() http.Handler {
func (a *api) deletePostHandler(isDraft bool) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@ -338,7 +353,13 @@ func (a *api) deletePostHandler() http.Handler {
return
}
err := a.params.PostStore.Delete(id)
var err error
if isDraft {
err = a.params.PostDraftStore.Delete(id)
} else {
err = a.params.PostStore.Delete(id)
}
if errors.Is(err, post.ErrPostNotFound) {
http.Error(rw, "Post not found", 404)
@ -350,8 +371,11 @@ func (a *api) deletePostHandler() http.Handler {
return
}
a.executeRedirectTpl(rw, r, a.postsURL(false))
if isDraft {
a.executeRedirectTpl(rw, r, a.draftsURL(false))
} else {
a.executeRedirectTpl(rw, r, a.postsURL(false))
}
})
}

@ -57,6 +57,15 @@ func (a *api) assetsURL(abs bool) string {
return a.blogURL("assets", abs)
}
func (a *api) draftURL(id string, abs bool) string {
path := filepath.Join("drafts", id)
return a.blogURL(path, abs)
}
func (a *api) draftsURL(abs bool) string {
return a.blogURL("drafts", abs)
}
func (a *api) tplFuncs() template.FuncMap {
return template.FuncMap{
"BlogURL": func(path string) string {
@ -71,12 +80,15 @@ func (a *api) tplFuncs() template.FuncMap {
b, err := staticFS.ReadFile(path)
return template.CSS(b), err
},
"PostURL": func(id string) string {
return a.postURL(id, false)
},
"AssetURL": func(id string) string {
path := filepath.Join("assets", id)
return a.blogURL(path, false)
},
"PostURL": func(id string) string {
return a.postURL(id, false)
"DraftURL": func(id string) string {
return a.draftURL(id, false)
},
"DateTimeFormat": func(t time.Time) string {
return t.Format("2006-01-02")

@ -9,6 +9,7 @@ anything without providing credentials.
<ul>
<li><a href="{{ BlogURL "posts" }}">Posts</a></li>
<li><a href="{{ BlogURL "assets" }}">Assets</a></li>
<li><a href="{{ BlogURL "drafts" }}">Drafts (private)</a></li>
</ul>
{{ end }}

@ -0,0 +1,48 @@
{{ define "body" }}
<h1>Drafts</h1>
<p>
<a href="{{ BlogURL "drafts/" }}?edit">
New Draft
</a>
</p>
{{ if ge .Payload.PrevPage 0 }}
<p>
<a href="?p={{ .Payload.PrevPage}}">&lt; &lt; Previous Page</a>
</p>
{{ end }}
<table>
{{ range .Payload.Posts }}
<tr>
<td><a href="{{ DraftURL .ID }}">{{ .Title }}</a></td>
<td>
<a href="{{ DraftURL .ID }}?edit">
Edit
</a>
</td>
<td>
<form
action="{{ DraftURL .ID }}?method=delete"
method="POST"
>
<input type="submit" value="Delete" />
</form>
</td>
</tr>
{{ end }}
</table>
{{ if ge .Payload.NextPage 0 }}
<p>
<a href="?p={{ .Payload.NextPage}}">Next Page &gt; &gt;</a>
</p>
{{ end }}
{{ end }}
{{ template "base.html" . }}

@ -15,6 +15,9 @@
type="text"
placeholder="e.g. how-to-fly-a-kite"
value="{{ .Payload.Post.ID }}" />
{{ else if .Payload.IsDraft }}
{{ .Payload.Post.ID }}
<input name="id" type="hidden" value="{{ .Payload.Post.ID }}" />
{{ else }}
<a href="{{ PostURL .Payload.Post.ID }}">{{ .Payload.Post.ID }}</a>
<input name="id" type="hidden" value="{{ .Payload.Post.ID }}" />
@ -107,10 +110,17 @@
</form>
<p>
{{ if .Payload.IsDraft }}
<a href="{{ BlogURL "drafts/" }}">
Back to Drafts
</a>
{{ else }}
<a href="{{ BlogURL "posts/" }}">
Back to Posts
</a>
{{ end }}
</p>
{{ end }}
{{ template "base.html" . }}

@ -88,6 +88,7 @@ func (s *draftStore) get(
SELECT
p.id, p.title, p.description, p.tags, p.series, p.body
FROM post_drafts p
` + where + `
ORDER BY p.id ASC`
if limit > 0 {

Loading…
Cancel
Save