Move asset store into its own package
This commit is contained in:
parent
68f3215df6
commit
7872296b83
@ -9,6 +9,7 @@ import (
|
||||
|
||||
cfgpkg "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -99,7 +100,7 @@ func main() {
|
||||
}
|
||||
|
||||
{
|
||||
assetStore := post.NewAssetStore(postDB)
|
||||
assetStore := asset.NewStore(postDB)
|
||||
|
||||
setAsset := func(assetID, assetPath string) error {
|
||||
assetFullPath := filepath.Join(testDataDir, assetPath)
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/http"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/pow"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||
@ -100,7 +101,7 @@ func main() {
|
||||
defer postSQLDB.Close()
|
||||
|
||||
postStore := post.NewStore(postSQLDB)
|
||||
postAssetStore := post.NewAssetStore(postSQLDB)
|
||||
postAssetStore := asset.NewStore(postSQLDB)
|
||||
postDraftStore := post.NewDraftStore(postSQLDB)
|
||||
|
||||
cache := cache.New(5000)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/cache"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||
)
|
||||
@ -27,7 +28,7 @@ type Params struct {
|
||||
Cache cache.Cache
|
||||
|
||||
PostStore post.Store
|
||||
PostAssetStore post.AssetStore
|
||||
PostAssetStore asset.Store
|
||||
|
||||
PublicURL *url.URL
|
||||
ListenAddr string
|
||||
@ -218,7 +219,7 @@ func (a *api) assetsMiddleware() gemini.Handler {
|
||||
|
||||
err := a.params.PostAssetStore.Get(id, rw)
|
||||
|
||||
if errors.Is(err, post.ErrAssetNotFound) {
|
||||
if errors.Is(err, asset.ErrNotFound) {
|
||||
rw.WriteHeader(gemini.StatusNotFound, "Asset not found, sorry!")
|
||||
return
|
||||
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
|
||||
"github.com/omeid/go-tarfs"
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
@ -170,7 +170,7 @@ func (a *api) handleGetPostAssetArchive(
|
||||
|
||||
err := a.params.PostAssetStore.Get(info.id, buf)
|
||||
|
||||
if errors.Is(err, post.ErrAssetNotFound) {
|
||||
if errors.Is(err, asset.ErrNotFound) {
|
||||
http.Error(rw, "asset not found", 404)
|
||||
return
|
||||
} else if err != nil {
|
||||
@ -244,7 +244,7 @@ func (a *api) getPostAssetHandler() http.Handler {
|
||||
|
||||
err := a.params.PostAssetStore.Get(id, buf)
|
||||
|
||||
if errors.Is(err, post.ErrAssetNotFound) {
|
||||
if errors.Is(err, asset.ErrNotFound) {
|
||||
http.Error(rw, "Asset not found", 404)
|
||||
return
|
||||
} else if err != nil {
|
||||
@ -297,7 +297,7 @@ func (a *api) deletePostAssetHandler() http.Handler {
|
||||
|
||||
err := a.params.PostAssetStore.Delete(id)
|
||||
|
||||
if errors.Is(err, post.ErrAssetNotFound) {
|
||||
if errors.Is(err, asset.ErrNotFound) {
|
||||
http.Error(rw, "Asset not found", 404)
|
||||
return
|
||||
} else if err != nil {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/pow"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||
@ -36,7 +37,7 @@ type Params struct {
|
||||
Cache cache.Cache
|
||||
|
||||
PostStore post.Store
|
||||
PostAssetStore post.AssetStore
|
||||
PostAssetStore asset.Store
|
||||
PostDraftStore post.DraftStore
|
||||
|
||||
MailingList mailinglist.MailingList
|
||||
|
@ -1,4 +1,4 @@
|
||||
package post
|
||||
package asset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -6,23 +6,25 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAssetNotFound is used to indicate an Asset could not be found in the
|
||||
// AssetStore.
|
||||
ErrAssetNotFound = errors.New("asset not found")
|
||||
// ErrNotFound is used to indicate an Asset could not be found in the
|
||||
// Store.
|
||||
ErrNotFound = errors.New("asset not found")
|
||||
)
|
||||
|
||||
// AssetStore implements the storage and retrieval of binary assets, which are
|
||||
// Store implements the storage and retrieval of binary assets, which are
|
||||
// intended to be used by posts (e.g. images).
|
||||
type AssetStore interface {
|
||||
type Store interface {
|
||||
|
||||
// Set sets the id to the contents of the given io.Reader.
|
||||
Set(id string, from io.Reader) error
|
||||
|
||||
// Get writes the id's body to the given io.Writer, or returns
|
||||
// ErrAssetNotFound.
|
||||
// ErrNotFound.
|
||||
Get(id string, into io.Writer) error
|
||||
|
||||
// Delete's the body stored for the id, if any.
|
||||
@ -32,18 +34,18 @@ type AssetStore interface {
|
||||
List() ([]string, error)
|
||||
}
|
||||
|
||||
type assetStore struct {
|
||||
db *sql.DB
|
||||
type store struct {
|
||||
db *post.SQLDB
|
||||
}
|
||||
|
||||
// NewAssetStore initializes a new AssetStore using an existing SQLDB.
|
||||
func NewAssetStore(db *SQLDB) AssetStore {
|
||||
return &assetStore{
|
||||
db: db.db,
|
||||
// NewStore initializes a new Store using an existing SQLDB.
|
||||
func NewStore(db *post.SQLDB) Store {
|
||||
return &store{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *assetStore) Set(id string, from io.Reader) error {
|
||||
func (s *store) Set(id string, from io.Reader) error {
|
||||
|
||||
body, err := io.ReadAll(from)
|
||||
if err != nil {
|
||||
@ -64,14 +66,14 @@ func (s *assetStore) Set(id string, from io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *assetStore) Get(id string, into io.Writer) error {
|
||||
func (s *store) Get(id string, into io.Writer) error {
|
||||
|
||||
var body []byte
|
||||
|
||||
err := s.db.QueryRow(`SELECT body FROM assets WHERE id = ?`, id).Scan(&body)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrAssetNotFound
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("selecting from assets: %w", err)
|
||||
}
|
||||
@ -83,12 +85,12 @@ func (s *assetStore) Get(id string, into io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *assetStore) Delete(id string) error {
|
||||
func (s *store) Delete(id string) error {
|
||||
_, err := s.db.Exec(`DELETE FROM assets WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *assetStore) List() ([]string, error) {
|
||||
func (s *store) List() ([]string, error) {
|
||||
|
||||
rows, err := s.db.Query(`SELECT id FROM assets ORDER BY id ASC`)
|
||||
|
@ -1,30 +1,31 @@
|
||||
package post
|
||||
package asset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type assetTestHarness struct {
|
||||
store AssetStore
|
||||
type testHarness struct {
|
||||
store Store
|
||||
}
|
||||
|
||||
func newAssetTestHarness(t *testing.T) *assetTestHarness {
|
||||
func newTestHarness(t *testing.T) *testHarness {
|
||||
|
||||
db := NewInMemSQLDB()
|
||||
db := post.NewInMemSQLDB()
|
||||
t.Cleanup(func() { db.Close() })
|
||||
|
||||
store := NewAssetStore(db)
|
||||
store := NewStore(db)
|
||||
|
||||
return &assetTestHarness{
|
||||
return &testHarness{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *assetTestHarness) assertGet(t *testing.T, exp, id string) {
|
||||
func (h *testHarness) assertGet(t *testing.T, exp, id string) {
|
||||
t.Helper()
|
||||
buf := new(bytes.Buffer)
|
||||
err := h.store.Get(id, buf)
|
||||
@ -32,15 +33,15 @@ func (h *assetTestHarness) assertGet(t *testing.T, exp, id string) {
|
||||
assert.Equal(t, exp, buf.String())
|
||||
}
|
||||
|
||||
func (h *assetTestHarness) assertNotFound(t *testing.T, id string) {
|
||||
func (h *testHarness) assertNotFound(t *testing.T, id string) {
|
||||
t.Helper()
|
||||
err := h.store.Get(id, io.Discard)
|
||||
assert.ErrorIs(t, ErrAssetNotFound, err)
|
||||
assert.ErrorIs(t, ErrNotFound, err)
|
||||
}
|
||||
|
||||
func TestAssetStore(t *testing.T) {
|
||||
func TestStore(t *testing.T) {
|
||||
|
||||
testAssetStore := func(t *testing.T, h *assetTestHarness) {
|
||||
testStore := func(t *testing.T, h *testHarness) {
|
||||
t.Helper()
|
||||
|
||||
h.assertNotFound(t, "foo")
|
||||
@ -85,7 +86,7 @@ func TestAssetStore(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("sql", func(t *testing.T) {
|
||||
h := newAssetTestHarness(t)
|
||||
testAssetStore(t, h)
|
||||
h := newTestHarness(t)
|
||||
testStore(t, h)
|
||||
})
|
||||
}
|
@ -47,7 +47,7 @@ func (s *draftStore) Set(post Post) error {
|
||||
return fmt.Errorf("json marshaling tags %#v: %w", post.Tags, err)
|
||||
}
|
||||
|
||||
_, err = s.db.db.Exec(
|
||||
_, err = s.db.Exec(
|
||||
`INSERT INTO post_drafts (
|
||||
id, title, description, tags, series, body, format
|
||||
)
|
||||
@ -145,7 +145,7 @@ func (s *draftStore) get(
|
||||
|
||||
func (s *draftStore) Get(page, count int) ([]Post, bool, error) {
|
||||
|
||||
posts, err := s.get(s.db.db, count+1, page*count, ``)
|
||||
posts, err := s.get(s.db, count+1, page*count, ``)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("querying post_drafts: %w", err)
|
||||
@ -163,7 +163,7 @@ func (s *draftStore) Get(page, count int) ([]Post, bool, error) {
|
||||
|
||||
func (s *draftStore) GetByID(id string) (Post, error) {
|
||||
|
||||
posts, err := s.get(s.db.db, 0, 0, `WHERE p.id=?`, id)
|
||||
posts, err := s.get(s.db, 0, 0, `WHERE p.id=?`, id)
|
||||
|
||||
if err != nil {
|
||||
return Post{}, fmt.Errorf("querying post_drafts: %w", err)
|
||||
@ -182,7 +182,7 @@ func (s *draftStore) GetByID(id string) (Post, error) {
|
||||
|
||||
func (s *draftStore) Delete(id string) error {
|
||||
|
||||
if _, err := s.db.db.Exec(`DELETE FROM post_drafts WHERE id = ?`, id); err != nil {
|
||||
if _, err := s.db.Exec(`DELETE FROM post_drafts WHERE id = ?`, id); err != nil {
|
||||
return fmt.Errorf("deleting from post_drafts: %w", err)
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ func (s *store) Set(post Post, now time.Time) (bool, error) {
|
||||
|
||||
var first bool
|
||||
|
||||
err := s.db.withTx(func(tx *sql.Tx) error {
|
||||
err := s.db.WithTx(func(tx *sql.Tx) error {
|
||||
|
||||
nowTS := now.Unix()
|
||||
|
||||
@ -244,7 +244,7 @@ func (s *store) get(
|
||||
|
||||
func (s *store) Get(page, count int) ([]StoredPost, bool, error) {
|
||||
|
||||
posts, err := s.get(s.db.db, count+1, page*count, ``)
|
||||
posts, err := s.get(s.db, count+1, page*count, ``)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("querying posts: %w", err)
|
||||
@ -262,7 +262,7 @@ func (s *store) Get(page, count int) ([]StoredPost, bool, error) {
|
||||
|
||||
func (s *store) GetByID(id string) (StoredPost, error) {
|
||||
|
||||
posts, err := s.get(s.db.db, 0, 0, `WHERE p.id=?`, id)
|
||||
posts, err := s.get(s.db, 0, 0, `WHERE p.id=?`, id)
|
||||
|
||||
if err != nil {
|
||||
return StoredPost{}, fmt.Errorf("querying posts: %w", err)
|
||||
@ -280,14 +280,14 @@ func (s *store) GetByID(id string) (StoredPost, error) {
|
||||
}
|
||||
|
||||
func (s *store) GetBySeries(series string) ([]StoredPost, error) {
|
||||
return s.get(s.db.db, 0, 0, `WHERE p.series=?`, series)
|
||||
return s.get(s.db, 0, 0, `WHERE p.series=?`, series)
|
||||
}
|
||||
|
||||
func (s *store) GetByTag(tag string) ([]StoredPost, error) {
|
||||
|
||||
var posts []StoredPost
|
||||
|
||||
err := s.db.withTx(func(tx *sql.Tx) error {
|
||||
err := s.db.WithTx(func(tx *sql.Tx) error {
|
||||
|
||||
rows, err := tx.Query(`SELECT post_id FROM post_tags WHERE tag = ?`, tag)
|
||||
|
||||
@ -331,7 +331,7 @@ func (s *store) GetByTag(tag string) ([]StoredPost, error) {
|
||||
|
||||
func (s *store) GetTags() ([]string, error) {
|
||||
|
||||
rows, err := s.db.db.Query(`SELECT tag FROM post_tags GROUP BY tag`)
|
||||
rows, err := s.db.Query(`SELECT tag FROM post_tags GROUP BY tag`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying all tags: %w", err)
|
||||
}
|
||||
@ -355,7 +355,7 @@ func (s *store) GetTags() ([]string, error) {
|
||||
|
||||
func (s *store) Delete(id string) error {
|
||||
|
||||
return s.db.withTx(func(tx *sql.Tx) error {
|
||||
return s.db.WithTx(func(tx *sql.Tx) error {
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM post_tags WHERE post_id = ?`, id); err != nil {
|
||||
return fmt.Errorf("deleting from post_tags: %w", err)
|
||||
|
@ -78,7 +78,7 @@ var migrations = &migrate.MemoryMigrationSource{Migrations: []*migrate.Migration
|
||||
// SQLDB is a sqlite3 database which can be used by storage interfaces within
|
||||
// this package.
|
||||
type SQLDB struct {
|
||||
db *sql.DB
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// NewSQLDB initializes and returns a new sqlite3 database for storage
|
||||
@ -116,12 +116,14 @@ func NewInMemSQLDB() *SQLDB {
|
||||
|
||||
// Close cleans up loose resources being held by the db.
|
||||
func (db *SQLDB) Close() error {
|
||||
return db.db.Close()
|
||||
return db.DB.Close()
|
||||
}
|
||||
|
||||
func (db *SQLDB) withTx(cb func(*sql.Tx) error) error {
|
||||
// WithTx initializes a transaction, runs the callback using it, and either
|
||||
// commits or rolls it back depending on if the callback returns an error.
|
||||
func (db *SQLDB) WithTx(cb func(*sql.Tx) error) error {
|
||||
|
||||
tx, err := db.db.Begin()
|
||||
tx, err := db.DB.Begin()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting transaction: %w", err)
|
||||
|
Loading…
Reference in New Issue
Block a user