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)
|
||||
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"))
|
||||
|
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"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 }}
|
||||
|
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"
|
||||
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…
Reference in New Issue
Block a user