Preview post
This commit is contained in:
parent
80dd345237
commit
5610c19be4
1
go.mod
1
go.mod
@ -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
5
go.sum
@ -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=
|
||||||
|
59
handler.go
59
handler.go
@ -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)
|
||||||
}
|
}
|
||||||
|
47
store.go
47
store.go
@ -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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user