diff options
author | Brian Picciano <mediocregopher@gmail.com> | 2022-09-13 12:56:08 +0200 |
---|---|---|
committer | Brian Picciano <mediocregopher@gmail.com> | 2022-09-13 12:56:08 +0200 |
commit | 4f01edb9230f58ff84b0dd892c931ec8ac9aad55 (patch) | |
tree | 9c1598a3f98203913ac2548883c02a81deb33dc7 /srv/src/http/posts.go | |
parent | 5485984e05aebde22819adebfbd5ad51475a6c21 (diff) |
move src out of srv, clean up default.nix and Makefile
Diffstat (limited to 'srv/src/http/posts.go')
-rw-r--r-- | srv/src/http/posts.go | 421 |
1 files changed, 0 insertions, 421 deletions
diff --git a/srv/src/http/posts.go b/srv/src/http/posts.go deleted file mode 100644 index c3f6363..0000000 --- a/srv/src/http/posts.go +++ /dev/null @@ -1,421 +0,0 @@ -package http - -import ( - "bytes" - "context" - "errors" - "fmt" - "html/template" - "net/http" - "path/filepath" - "strings" - txttpl "text/template" - "time" - - "github.com/gomarkdown/markdown" - "github.com/gomarkdown/markdown/html" - "github.com/gomarkdown/markdown/parser" - "github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil" - "github.com/mediocregopher/blog.mediocregopher.com/srv/post" - "github.com/mediocregopher/mediocre-go-lib/v2/mctx" -) - -func (a *api) parsePostBody(post post.Post) (*txttpl.Template, error) { - tpl := txttpl.New("root") - tpl = tpl.Funcs(txttpl.FuncMap(a.tplFuncs())) - - tpl = txttpl.Must(tpl.New("image.html").Parse(mustReadTplFile("image.html"))) - tpl = tpl.Funcs(txttpl.FuncMap{ - "Image": func(id string) (string, error) { - - tplPayload := struct { - ID string - Resizable bool - }{ - ID: id, - Resizable: isImgResizable(id), - } - - buf := new(bytes.Buffer) - if err := tpl.ExecuteTemplate(buf, "image.html", tplPayload); err != nil { - return "", err - } - - return buf.String(), nil - }, - }) - - tpl, err := tpl.New(post.ID + "-body.html").Parse(post.Body) - - if err != nil { - return nil, err - } - - return tpl, nil -} - -type postTplPayload struct { - post.StoredPost - SeriesPrevious, SeriesNext *post.StoredPost - Body template.HTML -} - -func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) { - - bodyTpl, err := a.parsePostBody(storedPost.Post) - if err != nil { - return postTplPayload{}, fmt.Errorf("parsing post body as template: %w", err) - } - - bodyBuf := new(bytes.Buffer) - - if err := bodyTpl.Execute(bodyBuf, nil); err != nil { - return postTplPayload{}, fmt.Errorf("executing post body as template: %w", err) - } - - // this helps the markdown renderer properly parse pages which end in a - // `</script>` tag... I don't know why. - _, _ = bodyBuf.WriteString("\n") - - parserExt := parser.CommonExtensions | parser.AutoHeadingIDs - parser := parser.NewWithExtensions(parserExt) - - htmlFlags := html.HrefTargetBlank - htmlRenderer := html.NewRenderer(html.RendererOptions{Flags: htmlFlags}) - - renderedBody := markdown.ToHTML(bodyBuf.Bytes(), parser, htmlRenderer) - - tplPayload := postTplPayload{ - StoredPost: storedPost, - Body: template.HTML(renderedBody), - } - - if series := storedPost.Series; series != "" { - - seriesPosts, err := a.params.PostStore.GetBySeries(series) - if err != nil { - return postTplPayload{}, fmt.Errorf( - "fetching posts for series %q: %w", series, err, - ) - } - - var foundThis bool - - for i := range seriesPosts { - - seriesPost := seriesPosts[i] - - if seriesPost.ID == storedPost.ID { - foundThis = true - continue - } - - if !foundThis { - tplPayload.SeriesNext = &seriesPost - continue - } - - tplPayload.SeriesPrevious = &seriesPost - break - } - } - - return tplPayload, nil -} - -func (a *api) renderPostHandler() 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) { - 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(storedPost) - - 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) renderPostsIndexHandler() http.Handler { - - renderEditPostHandler := a.renderEditPostHandler(false) - tpl := a.mustParseBasedTpl("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.PostStore.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.StoredPost - 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) - }) -} - -func (a *api) renderEditPostHandler(isDraft bool) http.Handler { - - tpl := a.mustParseBasedTpl("edit-post.html") - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - id := filepath.Base(r.URL.Path) - - var storedPost post.StoredPost - - 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) - return - } else if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("fetching post with id %q: %w", id, err), - ) - return - } - - } else if !isDraft { - http.Error(rw, "Post ID required in URL", 400) - return - } - - tags, err := a.params.PostStore.GetTags() - if err != nil { - apiutil.InternalServerError(rw, r, fmt.Errorf("fetching tags: %w", err)) - return - } - - tplPayload := struct { - Post post.StoredPost - Tags []string - IsDraft bool - }{ - Post: storedPost, - Tags: tags, - IsDraft: isDraft, - } - - executeTemplate(rw, r, tpl, tplPayload) - }) -} - -func postFromPostReq(r *http.Request) (post.Post, error) { - - p := post.Post{ - ID: r.PostFormValue("id"), - Title: r.PostFormValue("title"), - Description: r.PostFormValue("description"), - Tags: strings.Fields(r.PostFormValue("tags")), - Series: r.PostFormValue("series"), - } - - // textareas encode newlines as CRLF for historical reasons - p.Body = r.PostFormValue("body") - p.Body = strings.ReplaceAll(p.Body, "\r\n", "\n") - p.Body = strings.TrimSpace(p.Body) - - if p.ID == "" || - p.Title == "" || - p.Description == "" || - p.Body == "" || - len(p.Tags) == 0 { - return post.Post{}, errors.New("ID, Title, Description, Tags, and Body are all required") - } - - return p, nil -} - -func (a *api) storeAndPublishPost(ctx context.Context, p post.Post) error { - - first, err := a.params.PostStore.Set(p, time.Now()) - - if err != nil { - return fmt.Errorf("storing post with id %q: %w", p.ID, err) - } - - if !first { - return nil - } - - a.params.Logger.Info(ctx, "publishing blog post to mailing list") - urlStr := a.postURL(p.ID, true) - - if err := a.params.MailingList.Publish(p.Title, urlStr); err != nil { - return fmt.Errorf("publishing post to mailing list: %w", err) - } - - if err := a.params.PostDraftStore.Delete(p.ID); err != nil { - return fmt.Errorf("deleting draft: %w", err) - } - - return nil -} - -func (a *api) postPostHandler() http.Handler { - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - ctx := r.Context() - - p, err := postFromPostReq(r) - if err != nil { - apiutil.BadRequest(rw, r, err) - return - } - - ctx = mctx.Annotate(ctx, "postID", p.ID) - - if err := a.storeAndPublishPost(ctx, p); err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("storing/publishing post with id %q: %w", p.ID, err), - ) - return - } - - a.executeRedirectTpl(rw, r, a.postURL(p.ID, false)) - }) -} - -func (a *api) deletePostHandler(isDraft bool) http.Handler { - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - id := filepath.Base(r.URL.Path) - - if id == "/" { - apiutil.BadRequest(rw, r, errors.New("id is required")) - return - } - - 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) - return - } else if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("deleting post with id %q: %w", id, err), - ) - return - } - - if isDraft { - a.executeRedirectTpl(rw, r, a.draftsURL(false)) - } else { - a.executeRedirectTpl(rw, r, a.postsURL(false)) - } - }) -} - -func (a *api) previewPostHandler() http.Handler { - - tpl := a.mustParseBasedTpl("post.html") - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - p, err := postFromPostReq(r) - if err != nil { - apiutil.BadRequest(rw, r, err) - return - } - - storedPost := post.StoredPost{ - Post: p, - PublishedAt: time.Now(), - } - - tplPayload, err := a.postToPostTplPayload(storedPost) - - if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("generating template payload: %w", err), - ) - return - } - - executeTemplate(rw, r, tpl, tplPayload) - }) -} |