Publish new posts to mailing list

main
Brian Picciano 2 years ago
parent 3e67823205
commit 3059909deb
  1. 23
      srv/src/http/posts.go
  2. 24
      srv/src/post/post.go
  3. 20
      srv/src/post/post_test.go

@ -131,11 +131,17 @@ func (a *api) renderPostHandler() http.Handler {
func (a *api) renderPostsIndexHandler() http.Handler { func (a *api) renderPostsIndexHandler() http.Handler {
renderEditPostHandler := a.renderEditPostHandler()
tpl := a.mustParseBasedTpl("posts.html") tpl := a.mustParseBasedTpl("posts.html")
const pageCount = 20 const pageCount = 20
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 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) page, err := apiutil.StrToInt(r.FormValue("p"), 0)
if err != nil { if err != nil {
apiutil.BadRequest( apiutil.BadRequest(
@ -239,13 +245,28 @@ func (a *api) postPostHandler() http.Handler {
return 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( apiutil.InternalServerError(
rw, r, fmt.Errorf("storing post with id %q: %w", p.ID, err), rw, r, fmt.Errorf("storing post with id %q: %w", p.ID, err),
) )
return 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) redirectPath := fmt.Sprintf("posts/%s?edit", p.ID)
a.executeRedirectTpl(rw, r, redirectPath) a.executeRedirectTpl(rw, r, redirectPath)

@ -48,9 +48,10 @@ type StoredPost struct {
// Store is used for storing posts to a persistent storage. // Store is used for storing posts to a persistent storage.
type Store interface { type Store interface {
// Set sets the Post data into the storage, keyed by the Post's ID. It // Set sets the Post data into the storage, keyed by the Post's ID. If there
// overwrites a previous Post with the same ID, if there was one. // was not a previously existing Post with the same ID then Set returns
Set(post Post, now time.Time) error // 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 // Get returns count StoredPosts, sorted time descending, offset by the
// given page number. The returned boolean indicates if there are more pages // 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 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 == "" { 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() 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 nil
}) })
return first, err
} }
func (s *store) get( func (s *store) get(

@ -108,7 +108,9 @@ func TestStore(t *testing.T) {
post := testPost(0) post := testPost(0)
post.Tags = []string{"foo", "bar"} 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) gotPost, err := h.store.GetByID(post.ID)
assert.NoError(t, err) assert.NoError(t, err)
@ -129,7 +131,9 @@ func TestStore(t *testing.T) {
post.Body = "anything" post.Body = "anything"
post.Tags = []string{"bar", "baz"} 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) gotPost, err = h.store.GetByID(post.ID)
assert.NoError(t, err) assert.NoError(t, err)
@ -160,7 +164,8 @@ func TestStore(t *testing.T) {
} }
for _, post := range posts { 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) gotPosts, hasMore, err := h.store.Get(0, 2)
@ -174,7 +179,8 @@ func TestStore(t *testing.T) {
assertPostsEqual(t, posts[2:4], gotPosts) assertPostsEqual(t, posts[2:4], gotPosts)
posts = append([]StoredPost{h.testStoredPost(4)}, posts...) 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) gotPosts, hasMore, err = h.store.Get(1, 2)
assert.NoError(t, err) assert.NoError(t, err)
@ -204,7 +210,8 @@ func TestStore(t *testing.T) {
posts[2].Series = "bar" posts[2].Series = "bar"
for _, post := range posts { 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") fooPosts, err := h.store.GetBySeries("foo")
@ -238,7 +245,8 @@ func TestStore(t *testing.T) {
posts[2].Tags = []string{"bar"} posts[2].Tags = []string{"bar"}
for _, post := range posts { 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") fooPosts, err := h.store.GetByTag("foo")

Loading…
Cancel
Save