Use unix timestamps for post.Store
This commit is contained in:
parent
f7d72adfb5
commit
c99b37c5b3
@ -6,10 +6,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3" // we need dis
|
||||||
migrate "github.com/rubenv/sql-migrate"
|
migrate "github.com/rubenv/sql-migrate"
|
||||||
"github.com/tilinna/clock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -23,8 +23,8 @@ var (
|
|||||||
type StoredPost struct {
|
type StoredPost struct {
|
||||||
Post
|
Post
|
||||||
|
|
||||||
PublishedAt Date
|
PublishedAt time.Time
|
||||||
LastUpdatedAt Date
|
LastUpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL returns the relative URL of the StoredPost.
|
// URL returns the relative URL of the StoredPost.
|
||||||
@ -32,9 +32,9 @@ func (p StoredPost) URL() string {
|
|||||||
return path.Join(
|
return path.Join(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%d/%0d/%0d",
|
"%d/%0d/%0d",
|
||||||
p.PublishedAt.Year,
|
p.PublishedAt.Year(),
|
||||||
p.PublishedAt.Month,
|
p.PublishedAt.Month(),
|
||||||
p.PublishedAt.Day,
|
p.PublishedAt.Day(),
|
||||||
),
|
),
|
||||||
p.ID+".html",
|
p.ID+".html",
|
||||||
)
|
)
|
||||||
@ -45,7 +45,7 @@ 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. It
|
||||||
// overwrites a previous Post with the same ID, if there was one.
|
// overwrites a previous Post with the same ID, if there was one.
|
||||||
Set(Post) error
|
Set(post Post, now time.Time) error
|
||||||
|
|
||||||
// Get returns count StoredPosts, sorted time descending, offset by the given page
|
// Get returns count StoredPosts, sorted time descending, offset by the given page
|
||||||
// number. The returned boolean indicates if there are more pages or not.
|
// number. The returned boolean indicates if there are more pages or not.
|
||||||
@ -102,8 +102,6 @@ type StoreParams struct {
|
|||||||
|
|
||||||
// Path to the file the database will be stored at.
|
// Path to the file the database will be stored at.
|
||||||
DBFilePath string
|
DBFilePath string
|
||||||
|
|
||||||
Clock clock.Clock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type store struct {
|
type store struct {
|
||||||
@ -136,7 +134,7 @@ func (s *store) Close() error {
|
|||||||
return s.db.Close()
|
return s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the callback returns an error then the transaction is aborted
|
// if the callback returns an error then the transaction is aborted.
|
||||||
func (s *store) withTx(cb func(*sql.Tx) error) error {
|
func (s *store) withTx(cb func(*sql.Tx) error) error {
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
@ -164,9 +162,12 @@ func (s *store) withTx(cb func(*sql.Tx) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) Set(post Post) error {
|
func (s *store) Set(post Post, now time.Time) error {
|
||||||
return s.withTx(func(tx *sql.Tx) error {
|
return s.withTx(func(tx *sql.Tx) error {
|
||||||
currentDate := DateFromTime(s.params.Clock.Now())
|
|
||||||
|
nowTS := now.Unix()
|
||||||
|
|
||||||
|
nowSql := sql.NullInt64{Int64: nowTS, Valid: !now.IsZero()}
|
||||||
|
|
||||||
_, err := tx.Exec(
|
_, err := tx.Exec(
|
||||||
`INSERT INTO posts (
|
`INSERT INTO posts (
|
||||||
@ -184,9 +185,9 @@ func (s *store) Set(post Post) error {
|
|||||||
post.Title,
|
post.Title,
|
||||||
post.Description,
|
post.Description,
|
||||||
&sql.NullString{String: post.Series, Valid: post.Series != ""},
|
&sql.NullString{String: post.Series, Valid: post.Series != ""},
|
||||||
currentDate,
|
nowSql,
|
||||||
post.Body,
|
post.Body,
|
||||||
currentDate,
|
nowSql,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -257,13 +258,14 @@ func (s *store) get(
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
post StoredPost
|
post StoredPost
|
||||||
series, tag sql.NullString
|
series, tag sql.NullString
|
||||||
|
publishedAt, lastUpdatedAt sql.NullInt64
|
||||||
)
|
)
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&post.ID, &post.Title, &post.Description, &series, &tag,
|
&post.ID, &post.Title, &post.Description, &series, &tag,
|
||||||
&post.PublishedAt, &post.LastUpdatedAt,
|
&publishedAt, &lastUpdatedAt,
|
||||||
&post.Body,
|
&post.Body,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -283,6 +285,14 @@ func (s *store) get(
|
|||||||
|
|
||||||
post.Series = series.String
|
post.Series = series.String
|
||||||
|
|
||||||
|
if publishedAt.Valid {
|
||||||
|
post.PublishedAt = time.Unix(publishedAt.Int64, 0).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastUpdatedAt.Valid {
|
||||||
|
post.LastUpdatedAt = time.Unix(lastUpdatedAt.Int64, 0).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
posts = append(posts, post)
|
posts = append(posts, post)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ type storeTestHarness struct {
|
|||||||
|
|
||||||
func newStoreTestHarness(t *testing.T) storeTestHarness {
|
func newStoreTestHarness(t *testing.T) storeTestHarness {
|
||||||
|
|
||||||
clock := clock.NewMock(time.Now().Truncate(1 * time.Hour))
|
clock := clock.NewMock(time.Now().UTC().Truncate(1 * time.Hour))
|
||||||
|
|
||||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-post-store-test-")
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-post-store-test-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,7 +48,6 @@ func newStoreTestHarness(t *testing.T) storeTestHarness {
|
|||||||
|
|
||||||
store, err := NewStore(StoreParams{
|
store, err := NewStore(StoreParams{
|
||||||
DBFilePath: tmpFilePath,
|
DBFilePath: tmpFilePath,
|
||||||
Clock: clock,
|
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ func (h *storeTestHarness) testStoredPost(i int) StoredPost {
|
|||||||
post := testPost(i)
|
post := testPost(i)
|
||||||
return StoredPost{
|
return StoredPost{
|
||||||
Post: post,
|
Post: post,
|
||||||
PublishedAt: DateFromTime(h.clock.Now()),
|
PublishedAt: h.clock.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,41 +100,41 @@ func TestStore(t *testing.T) {
|
|||||||
t.Run("set_get_delete", func(t *testing.T) {
|
t.Run("set_get_delete", func(t *testing.T) {
|
||||||
h := newStoreTestHarness(t)
|
h := newStoreTestHarness(t)
|
||||||
|
|
||||||
nowDate := DateFromTime(h.clock.Now())
|
now := h.clock.Now().UTC()
|
||||||
|
|
||||||
post := testPost(0)
|
post := testPost(0)
|
||||||
post.Tags = []string{"foo", "bar"}
|
post.Tags = []string{"foo", "bar"}
|
||||||
|
|
||||||
assert.NoError(t, h.store.Set(post))
|
assert.NoError(t, h.store.Set(post, now))
|
||||||
|
|
||||||
gotPost, err := h.store.GetByID(post.ID)
|
gotPost, err := h.store.GetByID(post.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assertPostEqual(t, StoredPost{
|
assertPostEqual(t, StoredPost{
|
||||||
Post: post,
|
Post: post,
|
||||||
PublishedAt: nowDate,
|
PublishedAt: now,
|
||||||
}, gotPost)
|
}, gotPost)
|
||||||
|
|
||||||
// we will now try updating the post on a different day, and ensure it
|
// we will now try updating the post on a different day, and ensure it
|
||||||
// updates properly
|
// updates properly
|
||||||
|
|
||||||
h.clock.Add(24 * time.Hour)
|
h.clock.Add(24 * time.Hour)
|
||||||
newNowDate := DateFromTime(h.clock.Now())
|
newNow := h.clock.Now().UTC()
|
||||||
|
|
||||||
post.Title = "something else"
|
post.Title = "something else"
|
||||||
post.Series = "whatever"
|
post.Series = "whatever"
|
||||||
post.Body = "anything"
|
post.Body = "anything"
|
||||||
post.Tags = []string{"bar", "baz"}
|
post.Tags = []string{"bar", "baz"}
|
||||||
|
|
||||||
assert.NoError(t, h.store.Set(post))
|
assert.NoError(t, h.store.Set(post, newNow))
|
||||||
|
|
||||||
gotPost, err = h.store.GetByID(post.ID)
|
gotPost, err = h.store.GetByID(post.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assertPostEqual(t, StoredPost{
|
assertPostEqual(t, StoredPost{
|
||||||
Post: post,
|
Post: post,
|
||||||
PublishedAt: nowDate,
|
PublishedAt: now,
|
||||||
LastUpdatedAt: newNowDate,
|
LastUpdatedAt: newNow,
|
||||||
}, gotPost)
|
}, gotPost)
|
||||||
|
|
||||||
// delete the post, it should go away
|
// delete the post, it should go away
|
||||||
@ -148,6 +147,8 @@ func TestStore(t *testing.T) {
|
|||||||
t.Run("get", func(t *testing.T) {
|
t.Run("get", func(t *testing.T) {
|
||||||
h := newStoreTestHarness(t)
|
h := newStoreTestHarness(t)
|
||||||
|
|
||||||
|
now := h.clock.Now().UTC()
|
||||||
|
|
||||||
posts := []StoredPost{
|
posts := []StoredPost{
|
||||||
h.testStoredPost(0),
|
h.testStoredPost(0),
|
||||||
h.testStoredPost(1),
|
h.testStoredPost(1),
|
||||||
@ -156,7 +157,7 @@ func TestStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
assert.NoError(t, h.store.Set(post.Post))
|
assert.NoError(t, h.store.Set(post.Post, now))
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPosts, hasMore, err := h.store.Get(0, 2)
|
gotPosts, hasMore, err := h.store.Get(0, 2)
|
||||||
@ -170,7 +171,7 @@ func TestStore(t *testing.T) {
|
|||||||
assertPostsEqual(t, posts[2:4], gotPosts)
|
assertPostsEqual(t, posts[2:4], gotPosts)
|
||||||
|
|
||||||
posts = append(posts, h.testStoredPost(4))
|
posts = append(posts, h.testStoredPost(4))
|
||||||
assert.NoError(t, h.store.Set(posts[4].Post))
|
assert.NoError(t, h.store.Set(posts[4].Post, now))
|
||||||
|
|
||||||
gotPosts, hasMore, err = h.store.Get(1, 2)
|
gotPosts, hasMore, err = h.store.Get(1, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -186,6 +187,8 @@ func TestStore(t *testing.T) {
|
|||||||
t.Run("get_by_series", func(t *testing.T) {
|
t.Run("get_by_series", func(t *testing.T) {
|
||||||
h := newStoreTestHarness(t)
|
h := newStoreTestHarness(t)
|
||||||
|
|
||||||
|
now := h.clock.Now().UTC()
|
||||||
|
|
||||||
posts := []StoredPost{
|
posts := []StoredPost{
|
||||||
h.testStoredPost(0),
|
h.testStoredPost(0),
|
||||||
h.testStoredPost(1),
|
h.testStoredPost(1),
|
||||||
@ -198,7 +201,7 @@ 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))
|
assert.NoError(t, h.store.Set(post.Post, now))
|
||||||
}
|
}
|
||||||
|
|
||||||
fooPosts, err := h.store.GetBySeries("foo")
|
fooPosts, err := h.store.GetBySeries("foo")
|
||||||
@ -218,6 +221,8 @@ func TestStore(t *testing.T) {
|
|||||||
|
|
||||||
h := newStoreTestHarness(t)
|
h := newStoreTestHarness(t)
|
||||||
|
|
||||||
|
now := h.clock.Now().UTC()
|
||||||
|
|
||||||
posts := []StoredPost{
|
posts := []StoredPost{
|
||||||
h.testStoredPost(0),
|
h.testStoredPost(0),
|
||||||
h.testStoredPost(1),
|
h.testStoredPost(1),
|
||||||
@ -230,7 +235,7 @@ 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))
|
assert.NoError(t, h.store.Set(post.Post, now))
|
||||||
}
|
}
|
||||||
|
|
||||||
fooPosts, err := h.store.GetByTag("foo")
|
fooPosts, err := h.store.GetByTag("foo")
|
||||||
|
Loading…
Reference in New Issue
Block a user