From 7b7bdcf57a5fa1e02041e3ef563c55f31d908f67 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 17 May 2022 14:39:42 -0600 Subject: Initial implementation of admin assets page --- srv/src/api/api.go | 2 ++ srv/src/api/render.go | 34 +++++++++++++++++++ srv/src/api/tpl/admin/assets.html | 18 ++++++++++ srv/src/post/asset.go | 69 +++++++++------------------------------ srv/src/post/asset_test.go | 23 +++++++++---- 5 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 srv/src/api/tpl/admin/assets.html (limited to 'srv/src') diff --git a/srv/src/api/api.go b/srv/src/api/api.go index 0184ef1..8d54d46 100644 --- a/srv/src/api/api.go +++ b/srv/src/api/api.go @@ -200,6 +200,8 @@ func (a *api) handler() http.Handler { mux.Handle("/v2/assets/", a.servePostAssetHandler()) + mux.Handle("/v2/admin/assets.html", a.renderAdminAssets()) + var globalHandler http.Handler = mux globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler) diff --git a/srv/src/api/render.go b/srv/src/api/render.go index 0f45211..8fc2cb6 100644 --- a/srv/src/api/render.go +++ b/srv/src/api/render.go @@ -39,6 +39,10 @@ func (a *api) mustParseTpl(name string) *template.Template { tpl := template.New("").Funcs(template.FuncMap{ "BlogURL": blogURL, + "AssetURL": func(path string) string { + path = filepath.Join("assets", path) + return blogURL(path) + }, }) tpl = template.Must(tpl.Parse(mustRead(name))) @@ -192,3 +196,33 @@ func (a *api) renderDumbHandler(tplName string) http.Handler { } }) } + +func (a *api) renderAdminAssets() http.Handler { + + tpl := a.mustParseTpl("admin/assets.html") + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + + ids, err := a.params.PostAssetStore.List() + + if err != nil { + apiutil.InternalServerError( + rw, r, fmt.Errorf("getting list of asset ids: %w", err), + ) + return + } + + tplData := struct { + IDs []string + }{ + IDs: ids, + } + + if err := tpl.Execute(rw, tplData); err != nil { + apiutil.InternalServerError( + rw, r, fmt.Errorf("rendering: %w", err), + ) + return + } + }) +} diff --git a/srv/src/api/tpl/admin/assets.html b/srv/src/api/tpl/admin/assets.html new file mode 100644 index 0000000..d871a3e --- /dev/null +++ b/srv/src/api/tpl/admin/assets.html @@ -0,0 +1,18 @@ +{{ define "body" }} + + + + {{ range .IDs }} + + + + + {{ end }} + +
{{ . }} + Delete (TODO) +
+ +{{ end }} + +{{ template "base.html" . }} diff --git a/srv/src/post/asset.go b/srv/src/post/asset.go index 18af8f6..a7b605b 100644 --- a/srv/src/post/asset.go +++ b/srv/src/post/asset.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "sync" ) var ( @@ -28,6 +27,9 @@ type AssetStore interface { // Delete's the body stored for the id, if any. Delete(id string) error + + // List returns all ids which are currently stored. + List() ([]string, error) } type assetStore struct { @@ -86,68 +88,27 @@ func (s *assetStore) Delete(id string) error { return err } -//////////////////////////////////////////////////////////////////////////////// - -type cachedAssetStore struct { - inner AssetStore - m sync.Map -} - -// NewCachedAssetStore wraps an AssetStore in an in-memory cache. -func NewCachedAssetStore(assetStore AssetStore) AssetStore { - return &cachedAssetStore{ - inner: assetStore, - } -} - -func (s *cachedAssetStore) Set(id string, from io.Reader) error { +func (s *assetStore) List() ([]string, error) { - buf := new(bytes.Buffer) - from = io.TeeReader(from, buf) + rows, err := s.db.Query(`SELECT id FROM assets ORDER BY id ASC`) - if err := s.inner.Set(id, from); err != nil { - return err + if err != nil { + return nil, fmt.Errorf("querying: %w", err) } - s.m.Store(id, buf.Bytes()) - return nil -} - -func (s *cachedAssetStore) Get(id string, into io.Writer) error { + defer rows.Close() - if bodyI, ok := s.m.Load(id); ok { + var ids []string - if err, ok := bodyI.(error); ok { - return err - } + for rows.Next() { - if _, err := io.Copy(into, bytes.NewReader(bodyI.([]byte))); err != nil { - return fmt.Errorf("writing body to io.Writer: %w", err) + var id string + if err := rows.Scan(&id); err != nil { + return nil, fmt.Errorf("scanning row: %w", err) } - return nil + ids = append(ids, id) } - buf := new(bytes.Buffer) - into = io.MultiWriter(into, buf) - - if err := s.inner.Get(id, into); errors.Is(err, ErrAssetNotFound) { - s.m.Store(id, err) - return err - } else if err != nil { - return err - } - - s.m.Store(id, buf.Bytes()) - return nil -} - -func (s *cachedAssetStore) Delete(id string) error { - - if err := s.inner.Delete(id); err != nil { - return err - } - - s.m.Delete(id) - return nil + return ids, nil } diff --git a/srv/src/post/asset_test.go b/srv/src/post/asset_test.go index d0cff48..4d62d46 100644 --- a/srv/src/post/asset_test.go +++ b/srv/src/post/asset_test.go @@ -65,16 +65,27 @@ func TestAssetStore(t *testing.T) { assert.NoError(t, h.store.Delete("bar")) h.assertNotFound(t, "foo") h.assertNotFound(t, "bar") + + // test list + + ids, err := h.store.List() + assert.NoError(t, err) + assert.Empty(t, ids) + + err = h.store.Set("foo", bytes.NewBufferString("FOOFOO")) + assert.NoError(t, err) + err = h.store.Set("foo", bytes.NewBufferString("FOOFOO")) + assert.NoError(t, err) + err = h.store.Set("bar", bytes.NewBufferString("FOOFOO")) + assert.NoError(t, err) + + ids, err = h.store.List() + assert.NoError(t, err) + assert.Equal(t, []string{"bar", "foo"}, ids) } t.Run("sql", func(t *testing.T) { h := newAssetTestHarness(t) testAssetStore(t, h) }) - - t.Run("mem", func(t *testing.T) { - h := newAssetTestHarness(t) - h.store = NewCachedAssetStore(h.store) - testAssetStore(t, h) - }) } -- cgit v1.2.3