package main import ( "fmt" "net/http" "strconv" h "gitlab.codemonkeysoftware.net/b/hatmill" ha "gitlab.codemonkeysoftware.net/b/hatmill/attribute" he "gitlab.codemonkeysoftware.net/b/hatmill/element" "github.com/russross/blackfriday/v2" ) const ( pathRoot = "/" pathCreatePost = "/create-post" pathDoCreatePost = "/create-post/do" pathPreviewPost = "/preview-post" ) type handler struct { mux *http.ServeMux store *Store baseURL string } func NewHandler(store *Store, baseURL string) http.Handler { h := &handler{ store: store, mux: http.NewServeMux(), baseURL: baseURL, } h.mux.HandleFunc(pathRoot, h.getPosts) h.mux.HandleFunc(pathCreatePost, h.createPost) h.mux.HandleFunc(pathDoCreatePost, h.doCreatePost) h.mux.HandleFunc(pathPreviewPost, h.previewPost) return h } func (hnd *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { hnd.mux.ServeHTTP(w, r) } func (hnd *handler) getPosts(w http.ResponseWriter, r *http.Request) { result, err := hnd.store.GetPosts(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var rows h.Terms for _, post := range result.Posts { previewURL := fmt.Sprintf("%s?%s=%d", pathPreviewPost, fieldNamePostID, post.Id) rows = append(rows, he.Tr()( he.Td()(h.Text(strconv.FormatInt(post.Id, 10))), he.Td()(h.Text(post.CreatedAt)), he.Td()(h.Text(post.UpdatedAt)), he.Td()(h.Text(post.Author)), he.Td()(h.Text(post.Title)), he.Td()( he.A(ha.Href(previewURL))(h.Text("Preview")), ), )) } html := he.Html()( he.Head()(), he.Body()( he.H1()(h.Text("Posts")), he.Table()( he.Thead()( he.Tr()( he.Th()(h.Text("ID")), he.Th()(h.Text("Created")), he.Th()(h.Text("Updated")), he.Th()(h.Text("Author")), he.Th()(h.Text("Title")), he.Th()(), ), ), he.Tbody()( rows..., ), ), he.A(ha.Href(hnd.baseURL+pathCreatePost))(h.Text("Create post")), ), ) _, err = h.WriteDocument(w, html) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } 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 ( fieldNamePostTitle = "post-title" fieldNamePostBody = "post-body" fieldNamePostAuthor = "post-author" ) func (hnd *handler) createPost(w http.ResponseWriter, r *http.Request) { html := he.Html()( he.Head()(), he.Body()( he.H1()(h.Text("Create 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.Input(ha.Name(fieldNamePostTitle)), he.Br(), he.Label(ha.For(fieldNamePostAuthor))(h.Text("Author")), he.Input(ha.Name(fieldNamePostAuthor)), he.Br(), he.Label(ha.For(fieldNamePostBody))(h.Text("Body")), he.Textarea(ha.Name(fieldNamePostBody))(), he.Br(), he.Input(ha.Type("submit")), ), ), ) _, err := h.WriteDocument(w, html) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (hnd *handler) doCreatePost(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(0) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } cmd := CreatePostCommand{ Title: r.PostForm.Get(fieldNamePostTitle), Author: r.PostForm.Get(fieldNamePostAuthor), Body: r.PostForm.Get(fieldNamePostBody), } _, err = hnd.store.CreatePost(r.Context(), cmd) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, pathRoot, http.StatusFound) }