drafts functionality added, needs a publish button still
This commit is contained in:
parent
dfa9bcb9e2
commit
c3135306b3
@ -121,11 +121,13 @@ func main() {
|
|||||||
|
|
||||||
postStore := post.NewStore(postSQLDB)
|
postStore := post.NewStore(postSQLDB)
|
||||||
postAssetStore := post.NewAssetStore(postSQLDB)
|
postAssetStore := post.NewAssetStore(postSQLDB)
|
||||||
|
postDraftStore := post.NewDraftStore(postSQLDB)
|
||||||
|
|
||||||
httpParams.Logger = logger.WithNamespace("http")
|
httpParams.Logger = logger.WithNamespace("http")
|
||||||
httpParams.PowManager = powMgr
|
httpParams.PowManager = powMgr
|
||||||
httpParams.PostStore = postStore
|
httpParams.PostStore = postStore
|
||||||
httpParams.PostAssetStore = postAssetStore
|
httpParams.PostAssetStore = postAssetStore
|
||||||
|
httpParams.PostDraftStore = postDraftStore
|
||||||
httpParams.MailingList = ml
|
httpParams.MailingList = ml
|
||||||
httpParams.GlobalRoom = chatGlobalRoom
|
httpParams.GlobalRoom = chatGlobalRoom
|
||||||
httpParams.UserIDCalculator = chatUserIDCalc
|
httpParams.UserIDCalculator = chatUserIDCalc
|
||||||
|
@ -37,6 +37,7 @@ type Params struct {
|
|||||||
|
|
||||||
PostStore post.Store
|
PostStore post.Store
|
||||||
PostAssetStore post.AssetStore
|
PostAssetStore post.AssetStore
|
||||||
|
PostDraftStore post.DraftStore
|
||||||
|
|
||||||
MailingList mailinglist.MailingList
|
MailingList mailinglist.MailingList
|
||||||
|
|
||||||
@ -201,8 +202,8 @@ func (a *api) blogHandler() http.Handler {
|
|||||||
mux.Handle("/posts/", http.StripPrefix("/posts",
|
mux.Handle("/posts/", http.StripPrefix("/posts",
|
||||||
apiutil.MethodMux(map[string]http.Handler{
|
apiutil.MethodMux(map[string]http.Handler{
|
||||||
"GET": a.renderPostHandler(),
|
"GET": a.renderPostHandler(),
|
||||||
"POST": a.postPostHandler(),
|
"POST": a.postPostHandler(false),
|
||||||
"DELETE": a.deletePostHandler(),
|
"DELETE": a.deletePostHandler(false),
|
||||||
"PREVIEW": a.previewPostHandler(),
|
"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("/static/", http.FileServer(http.FS(staticFS)))
|
||||||
mux.Handle("/follow", a.renderDumbTplHandler("follow.html"))
|
mux.Handle("/follow", a.renderDumbTplHandler("follow.html"))
|
||||||
mux.Handle("/admin", a.renderDumbTplHandler("admin.html"))
|
mux.Handle("/admin", a.renderDumbTplHandler("admin.html"))
|
||||||
|
110
srv/src/http/drafts.go
Normal file
110
srv/src/http/drafts.go
Normal file
@ -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"
|
"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 := txttpl.New("root")
|
||||||
tpl = tpl.Funcs(txttpl.FuncMap(a.tplFuncs()))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -60,7 +60,7 @@ type postTplPayload struct {
|
|||||||
|
|
||||||
func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) {
|
func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) {
|
||||||
|
|
||||||
bodyTpl, err := a.parsePostBody(storedPost)
|
bodyTpl, err := a.parsePostBody(storedPost.Post)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return postTplPayload{}, fmt.Errorf("parsing post body as template: %w", err)
|
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 != "/" {
|
if id != "/" {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
if isDraft {
|
||||||
|
storedPost.Post, err = a.params.PostDraftStore.GetByID(id)
|
||||||
|
} else {
|
||||||
storedPost, err = a.params.PostStore.GetByID(id)
|
storedPost, err = a.params.PostStore.GetByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, post.ErrPostNotFound) {
|
if errors.Is(err, post.ErrPostNotFound) {
|
||||||
http.Error(rw, "Post not found", 404)
|
http.Error(rw, "Post not found", 404)
|
||||||
@ -291,7 +296,7 @@ func postFromPostReq(r *http.Request) (post.Post, error) {
|
|||||||
return p, nil
|
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) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
@ -301,7 +306,13 @@ func (a *api) postPostHandler() http.Handler {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
apiutil.InternalServerError(
|
apiutil.InternalServerError(
|
||||||
@ -310,7 +321,7 @@ func (a *api) postPostHandler() http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if first {
|
if !isDraft && first {
|
||||||
|
|
||||||
a.params.Logger.Info(r.Context(), "publishing blog post to mailing list")
|
a.params.Logger.Info(r.Context(), "publishing blog post to mailing list")
|
||||||
urlStr := a.postURL(p.ID, true)
|
urlStr := a.postURL(p.ID, true)
|
||||||
@ -323,11 +334,15 @@ func (a *api) postPostHandler() http.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isDraft {
|
||||||
|
a.executeRedirectTpl(rw, r, a.draftURL(p.ID, false)+"?edit")
|
||||||
|
} else {
|
||||||
a.executeRedirectTpl(rw, r, a.postURL(p.ID, false)+"?edit")
|
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) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
@ -338,7 +353,13 @@ func (a *api) deletePostHandler() http.Handler {
|
|||||||
return
|
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) {
|
if errors.Is(err, post.ErrPostNotFound) {
|
||||||
http.Error(rw, "Post not found", 404)
|
http.Error(rw, "Post not found", 404)
|
||||||
@ -350,8 +371,11 @@ func (a *api) deletePostHandler() http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isDraft {
|
||||||
|
a.executeRedirectTpl(rw, r, a.draftsURL(false))
|
||||||
|
} else {
|
||||||
a.executeRedirectTpl(rw, r, a.postsURL(false))
|
a.executeRedirectTpl(rw, r, a.postsURL(false))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,15 @@ func (a *api) assetsURL(abs bool) string {
|
|||||||
return a.blogURL("assets", abs)
|
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 {
|
func (a *api) tplFuncs() template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
"BlogURL": func(path string) string {
|
"BlogURL": func(path string) string {
|
||||||
@ -71,12 +80,15 @@ func (a *api) tplFuncs() template.FuncMap {
|
|||||||
b, err := staticFS.ReadFile(path)
|
b, err := staticFS.ReadFile(path)
|
||||||
return template.CSS(b), err
|
return template.CSS(b), err
|
||||||
},
|
},
|
||||||
|
"PostURL": func(id string) string {
|
||||||
|
return a.postURL(id, false)
|
||||||
|
},
|
||||||
"AssetURL": func(id string) string {
|
"AssetURL": func(id string) string {
|
||||||
path := filepath.Join("assets", id)
|
path := filepath.Join("assets", id)
|
||||||
return a.blogURL(path, false)
|
return a.blogURL(path, false)
|
||||||
},
|
},
|
||||||
"PostURL": func(id string) string {
|
"DraftURL": func(id string) string {
|
||||||
return a.postURL(id, false)
|
return a.draftURL(id, false)
|
||||||
},
|
},
|
||||||
"DateTimeFormat": func(t time.Time) string {
|
"DateTimeFormat": func(t time.Time) string {
|
||||||
return t.Format("2006-01-02")
|
return t.Format("2006-01-02")
|
||||||
|
@ -9,6 +9,7 @@ anything without providing credentials.
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ BlogURL "posts" }}">Posts</a></li>
|
<li><a href="{{ BlogURL "posts" }}">Posts</a></li>
|
||||||
<li><a href="{{ BlogURL "assets" }}">Assets</a></li>
|
<li><a href="{{ BlogURL "assets" }}">Assets</a></li>
|
||||||
|
<li><a href="{{ BlogURL "drafts" }}">Drafts (private)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
48
srv/src/http/tpl/draft-posts.html
Normal file
48
srv/src/http/tpl/draft-posts.html
Normal file
@ -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}}">< < 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 > ></a>
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ template "base.html" . }}
|
@ -15,6 +15,9 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="e.g. how-to-fly-a-kite"
|
placeholder="e.g. how-to-fly-a-kite"
|
||||||
value="{{ .Payload.Post.ID }}" />
|
value="{{ .Payload.Post.ID }}" />
|
||||||
|
{{ else if .Payload.IsDraft }}
|
||||||
|
{{ .Payload.Post.ID }}
|
||||||
|
<input name="id" type="hidden" value="{{ .Payload.Post.ID }}" />
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="{{ PostURL .Payload.Post.ID }}">{{ .Payload.Post.ID }}</a>
|
<a href="{{ PostURL .Payload.Post.ID }}">{{ .Payload.Post.ID }}</a>
|
||||||
<input name="id" type="hidden" value="{{ .Payload.Post.ID }}" />
|
<input name="id" type="hidden" value="{{ .Payload.Post.ID }}" />
|
||||||
@ -107,10 +110,17 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
{{ if .Payload.IsDraft }}
|
||||||
|
<a href="{{ BlogURL "drafts/" }}">
|
||||||
|
Back to Drafts
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
<a href="{{ BlogURL "posts/" }}">
|
<a href="{{ BlogURL "posts/" }}">
|
||||||
Back to Posts
|
Back to Posts
|
||||||
</a>
|
</a>
|
||||||
|
{{ end }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
@ -88,6 +88,7 @@ func (s *draftStore) get(
|
|||||||
SELECT
|
SELECT
|
||||||
p.id, p.title, p.description, p.tags, p.series, p.body
|
p.id, p.title, p.description, p.tags, p.series, p.body
|
||||||
FROM post_drafts p
|
FROM post_drafts p
|
||||||
|
` + where + `
|
||||||
ORDER BY p.id ASC`
|
ORDER BY p.id ASC`
|
||||||
|
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user