Preview post

This commit is contained in:
Brandon Dyck 2022-11-19 00:11:58 -07:00
parent 80dd345237
commit 5610c19be4
4 changed files with 97 additions and 15 deletions

1
go.mod
View File

@ -4,5 +4,6 @@ go 1.18
require ( require (
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c
github.com/russross/blackfriday/v2 v2.1.0
gitlab.codemonkeysoftware.net/b/hatmill v0.0.6 gitlab.codemonkeysoftware.net/b/hatmill v0.0.6
) )

5
go.sum
View File

@ -1,9 +1,10 @@
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
crawshaw.io/sqlite v0.3.2 h1:N6IzTjkiw9FItHAa0jp+ZKC6tuLzXqAYIv+ccIWos1I=
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs= crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs=
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
github.com/leanovate/gopter v0.2.4 h1:U4YLBggDFhJdqQsG4Na2zX7joVTky9vHaj/AGEwSuXU= github.com/leanovate/gopter v0.2.4 h1:U4YLBggDFhJdqQsG4Na2zX7joVTky9vHaj/AGEwSuXU=
github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
gitlab.codemonkeysoftware.net/b/hatmill v0.0.6 h1:5Vs30ORHoujCYRvtbIwrusGBQgPzbKv011xKrTFa5ng= gitlab.codemonkeysoftware.net/b/hatmill v0.0.6 h1:5Vs30ORHoujCYRvtbIwrusGBQgPzbKv011xKrTFa5ng=
gitlab.codemonkeysoftware.net/b/hatmill v0.0.6/go.mod h1:T19ms3BmsEzy5YTS4icMO1oLC53xWhOP9MFeBv4NcFQ= gitlab.codemonkeysoftware.net/b/hatmill v0.0.6/go.mod h1:T19ms3BmsEzy5YTS4icMO1oLC53xWhOP9MFeBv4NcFQ=

View File

@ -1,19 +1,22 @@
package main package main
import ( import (
"log" "fmt"
"net/http" "net/http"
"strconv" "strconv"
h "gitlab.codemonkeysoftware.net/b/hatmill" h "gitlab.codemonkeysoftware.net/b/hatmill"
ha "gitlab.codemonkeysoftware.net/b/hatmill/attribute" ha "gitlab.codemonkeysoftware.net/b/hatmill/attribute"
he "gitlab.codemonkeysoftware.net/b/hatmill/element" he "gitlab.codemonkeysoftware.net/b/hatmill/element"
"github.com/russross/blackfriday/v2"
) )
const ( const (
pathRoot = "/" pathRoot = "/"
pathCreatePost = "/create-post" pathCreatePost = "/create-post"
pathDoCreatePost = "/create-post/do" pathDoCreatePost = "/create-post/do"
pathPreviewPost = "/preview-post"
) )
type handler struct { type handler struct {
@ -32,6 +35,7 @@ func NewHandler(store *Store, baseURL string) http.Handler {
h.mux.HandleFunc(pathRoot, h.getPosts) h.mux.HandleFunc(pathRoot, h.getPosts)
h.mux.HandleFunc(pathCreatePost, h.createPost) h.mux.HandleFunc(pathCreatePost, h.createPost)
h.mux.HandleFunc(pathDoCreatePost, h.doCreatePost) h.mux.HandleFunc(pathDoCreatePost, h.doCreatePost)
h.mux.HandleFunc(pathPreviewPost, h.previewPost)
return h return h
} }
@ -49,13 +53,16 @@ func (hnd *handler) getPosts(w http.ResponseWriter, r *http.Request) {
var rows h.Terms var rows h.Terms
for _, post := range result.Posts { for _, post := range result.Posts {
previewURL := fmt.Sprintf("%s?%s=%d", pathPreviewPost, fieldNamePostID, post.Id)
rows = append(rows, he.Tr()( rows = append(rows, he.Tr()(
he.Td()(h.Text(strconv.FormatInt(post.Id, 10))), he.Td()(h.Text(strconv.FormatInt(post.Id, 10))),
he.Td()(h.Text(post.CreatedAt)), he.Td()(h.Text(post.CreatedAt)),
he.Td()(h.Text(post.UpdatedAt)), he.Td()(h.Text(post.UpdatedAt)),
he.Td()(h.Text(post.Author)), he.Td()(h.Text(post.Author)),
he.Td()(h.Text(post.Title)), he.Td()(h.Text(post.Title)),
// he.Td()(h.Text(post.Body)), he.Td()(
he.A(ha.Href(previewURL))(h.Text("Preview")),
),
)) ))
} }
@ -71,6 +78,7 @@ func (hnd *handler) getPosts(w http.ResponseWriter, r *http.Request) {
he.Th()(h.Text("Updated")), he.Th()(h.Text("Updated")),
he.Th()(h.Text("Author")), he.Th()(h.Text("Author")),
he.Th()(h.Text("Title")), he.Th()(h.Text("Title")),
he.Th()(),
), ),
), ),
he.Tbody()( he.Tbody()(
@ -88,6 +96,40 @@ func (hnd *handler) getPosts(w http.ResponseWriter, r *http.Request) {
} }
} }
const fieldNamePostID = "post-id"
func (hnd *handler) previewPost(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get(fieldNamePostID)
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid post ID: "+err.Error(), http.StatusBadRequest)
}
result, err := hnd.store.GetPost(r.Context(), GetPostQuery{PostID: id})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
renderedBody := string(blackfriday.Run([]byte(result.Body)))
html := he.Html()(
he.Head()(
he.Title()(h.Text(result.Title)),
),
he.Body()(
he.H1()(h.Text(result.Title)),
he.H2()(h.Text(result.Author)),
he.H3()(h.Text(fmt.Sprintf("%s (Updated %s)", result.CreatedAt, result.UpdatedAt))),
he.Main()(h.RawText(renderedBody)),
),
)
_, err = h.WriteDocument(w, html)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
const ( const (
fieldNamePostTitle = "post-title" fieldNamePostTitle = "post-title"
fieldNamePostBody = "post-body" fieldNamePostBody = "post-body"
@ -99,7 +141,11 @@ func (hnd *handler) createPost(w http.ResponseWriter, r *http.Request) {
he.Head()(), he.Head()(),
he.Body()( he.Body()(
he.H1()(h.Text("Create post")), he.H1()(h.Text("Create post")),
he.Form(ha.Action(hnd.baseURL+pathDoCreatePost), ha.Method("POST"))( he.Form(
ha.Action(hnd.baseURL+pathDoCreatePost),
ha.Method("POST"),
ha.Enctype("multipart/form-data"),
)(
he.Label(ha.For(fieldNamePostTitle))(h.Text("Title")), he.Label(ha.For(fieldNamePostTitle))(h.Text("Title")),
he.Input(ha.Name(fieldNamePostTitle)), he.Input(ha.Name(fieldNamePostTitle)),
he.Br(), he.Br(),
@ -107,7 +153,7 @@ func (hnd *handler) createPost(w http.ResponseWriter, r *http.Request) {
he.Input(ha.Name(fieldNamePostAuthor)), he.Input(ha.Name(fieldNamePostAuthor)),
he.Br(), he.Br(),
he.Label(ha.For(fieldNamePostBody))(h.Text("Body")), he.Label(ha.For(fieldNamePostBody))(h.Text("Body")),
he.Textarea(ha.For(fieldNamePostBody))(), he.Textarea(ha.Name(fieldNamePostBody))(),
he.Br(), he.Br(),
he.Input(ha.Type("submit")), he.Input(ha.Type("submit")),
), ),
@ -121,7 +167,7 @@ func (hnd *handler) createPost(w http.ResponseWriter, r *http.Request) {
} }
func (hnd *handler) doCreatePost(w http.ResponseWriter, r *http.Request) { func (hnd *handler) doCreatePost(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseMultipartForm(0)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
@ -132,11 +178,10 @@ func (hnd *handler) doCreatePost(w http.ResponseWriter, r *http.Request) {
Author: r.PostForm.Get(fieldNamePostAuthor), Author: r.PostForm.Get(fieldNamePostAuthor),
Body: r.PostForm.Get(fieldNamePostBody), Body: r.PostForm.Get(fieldNamePostBody),
} }
result, err := hnd.store.CreatePost(r.Context(), cmd) _, err = hnd.store.CreatePost(r.Context(), cmd)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
log.Println("created post", result.PostId)
http.Redirect(w, r, pathRoot, http.StatusFound) http.Redirect(w, r, pathRoot, http.StatusFound)
} }

View File

@ -116,12 +116,12 @@ func (s *Store) GetPosts(ctx context.Context) (GetPostsResult, error) {
err := sqlitex.Exec(conn, dbQuery, err := sqlitex.Exec(conn, dbQuery,
func(stmt *sqlite.Stmt) error { func(stmt *sqlite.Stmt) error {
result.Posts = append(result.Posts, Post{ result.Posts = append(result.Posts, Post{
Id: stmt.ColumnInt64(0), Id: stmt.GetInt64("id"),
Author: stmt.ColumnText(1), Author: stmt.GetText("author"),
CreatedAt: stmt.ColumnText(2), CreatedAt: stmt.GetText("created_at"),
UpdatedAt: stmt.ColumnText(3), UpdatedAt: stmt.GetText("updated_at"),
Title: stmt.ColumnText(4), Title: stmt.GetText("title"),
Body: stmt.ColumnText(5), Body: stmt.GetText("body"),
}) })
return nil return nil
}) })
@ -161,3 +161,38 @@ func (s *Store) CreatePost(ctx context.Context, cmd CreatePostCommand) (CreatePo
}, cmd.Title, cmd.Author, cmd.Body) }, cmd.Title, cmd.Author, cmd.Body)
return result, err return result, err
} }
type GetPostQuery struct {
PostID int64
}
type GetPostResult struct {
Post
}
func (s *Store) GetPost(ctx context.Context, query GetPostQuery) (GetPostResult, error) {
conn := s.pool.Get(ctx)
if conn == nil {
return GetPostResult{}, ErrNoConn
}
defer s.pool.Put(conn)
const dbQuery = `
SELECT id, created_at, updated_at, author, title, body
FROM post
WHERE id = ?;`
var result GetPostResult
err := sqlitex.Exec(conn, dbQuery,
func(stmt *sqlite.Stmt) error {
result.Post = Post{
Id: stmt.GetInt64("id"),
CreatedAt: stmt.GetText("created_at"),
UpdatedAt: stmt.GetText("updated_at"),
Author: stmt.GetText("author"),
Title: stmt.GetText("title"),
Body: stmt.GetText("body"),
}
return nil
}, query.PostID)
return result, err
}