From 3059909deb5e21746d3d95e4989a78db51ccec33 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 21 May 2022 11:15:37 -0600 Subject: [PATCH] Publish new posts to mailing list --- srv/src/http/posts.go | 23 ++++++++++++++++++++++- srv/src/post/post.go | 24 ++++++++++++++++++------ srv/src/post/post_test.go | 20 ++++++++++++++------ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/srv/src/http/posts.go b/srv/src/http/posts.go index c779eed..daa756c 100644 --- a/srv/src/http/posts.go +++ b/srv/src/http/posts.go @@ -131,11 +131,17 @@ func (a *api) renderPostHandler() http.Handler { func (a *api) renderPostsIndexHandler() http.Handler { + renderEditPostHandler := a.renderEditPostHandler() 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( @@ -239,13 +245,28 @@ func (a *api) postPostHandler() http.Handler { return } - if err := a.params.PostStore.Set(p, time.Now()); err != nil { + first, err := a.params.PostStore.Set(p, time.Now()) + + if err != nil { apiutil.InternalServerError( rw, r, fmt.Errorf("storing post with id %q: %w", p.ID, err), ) return } + if first { + + a.params.Logger.Info(r.Context(), "publishing blog post to mailing list") + urlStr := a.params.PublicURL.String() + filepath.Join("/posts", p.ID) + + if err := a.params.MailingList.Publish(p.Title, urlStr); err != nil { + apiutil.InternalServerError( + rw, r, fmt.Errorf("publishing post with id %q: %w", p.ID, err), + ) + return + } + } + redirectPath := fmt.Sprintf("posts/%s?edit", p.ID) a.executeRedirectTpl(rw, r, redirectPath) diff --git a/srv/src/post/post.go b/srv/src/post/post.go index 29a984f..a39af61 100644 --- a/srv/src/post/post.go +++ b/srv/src/post/post.go @@ -48,9 +48,10 @@ type StoredPost struct { // Store is used for storing posts to a persistent storage. type Store interface { - // Set sets the Post data into the storage, keyed by the Post's ID. It - // overwrites a previous Post with the same ID, if there was one. - Set(post Post, now time.Time) error + // Set sets the Post data into the storage, keyed by the Post's ID. If there + // was not a previously existing Post with the same ID then Set returns + // true. It overwrites the previous Post with the same ID otherwise. + Set(post Post, now time.Time) (bool, error) // Get returns count StoredPosts, sorted time descending, offset by the // given page number. The returned boolean indicates if there are more pages @@ -114,13 +115,15 @@ func (s *store) withTx(cb func(*sql.Tx) error) error { return nil } -func (s *store) Set(post Post, now time.Time) error { +func (s *store) Set(post Post, now time.Time) (bool, error) { if post.ID == "" { - return errors.New("post ID can't be empty") + return false, errors.New("post ID can't be empty") } - return s.withTx(func(tx *sql.Tx) error { + var first bool + + err := s.withTx(func(tx *sql.Tx) error { nowTS := now.Unix() @@ -173,8 +176,17 @@ func (s *store) Set(post Post, now time.Time) error { } } + err = tx.QueryRow( + `SELECT 1 FROM posts WHERE id=? AND last_updated_at IS NULL`, + post.ID, + ).Scan(new(int)) + + first = !errors.Is(err, sql.ErrNoRows) + return nil }) + + return first, err } func (s *store) get( diff --git a/srv/src/post/post_test.go b/srv/src/post/post_test.go index b6d8a2e..c7f9cdc 100644 --- a/srv/src/post/post_test.go +++ b/srv/src/post/post_test.go @@ -108,7 +108,9 @@ func TestStore(t *testing.T) { post := testPost(0) post.Tags = []string{"foo", "bar"} - assert.NoError(t, h.store.Set(post, now)) + first, err := h.store.Set(post, now) + assert.NoError(t, err) + assert.True(t, first) gotPost, err := h.store.GetByID(post.ID) assert.NoError(t, err) @@ -129,7 +131,9 @@ func TestStore(t *testing.T) { post.Body = "anything" post.Tags = []string{"bar", "baz"} - assert.NoError(t, h.store.Set(post, newNow)) + first, err = h.store.Set(post, newNow) + assert.NoError(t, err) + assert.False(t, first) gotPost, err = h.store.GetByID(post.ID) assert.NoError(t, err) @@ -160,7 +164,8 @@ func TestStore(t *testing.T) { } for _, post := range posts { - assert.NoError(t, h.store.Set(post.Post, now)) + _, err := h.store.Set(post.Post, now) + assert.NoError(t, err) } gotPosts, hasMore, err := h.store.Get(0, 2) @@ -174,7 +179,8 @@ func TestStore(t *testing.T) { assertPostsEqual(t, posts[2:4], gotPosts) posts = append([]StoredPost{h.testStoredPost(4)}, posts...) - assert.NoError(t, h.store.Set(posts[0].Post, now)) + _, err = h.store.Set(posts[0].Post, now) + assert.NoError(t, err) gotPosts, hasMore, err = h.store.Get(1, 2) assert.NoError(t, err) @@ -204,7 +210,8 @@ func TestStore(t *testing.T) { posts[2].Series = "bar" for _, post := range posts { - assert.NoError(t, h.store.Set(post.Post, now)) + _, err := h.store.Set(post.Post, now) + assert.NoError(t, err) } fooPosts, err := h.store.GetBySeries("foo") @@ -238,7 +245,8 @@ func TestStore(t *testing.T) { posts[2].Tags = []string{"bar"} for _, post := range posts { - assert.NoError(t, h.store.Set(post.Post, now)) + _, err := h.store.Set(post.Post, now) + assert.NoError(t, err) } fooPosts, err := h.store.GetByTag("foo")