From 3037ea01e27c8f4272ba9eef0c6d7440dc11bf67 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 27 Oct 2020 10:03:10 -0400 Subject: [PATCH] Removed more layers of indirection from the engine package --- server/subsonic/album_lists.go | 91 ++++++++------ server/subsonic/album_lists_test.go | 55 +++------ server/subsonic/engine/common.go | 55 --------- server/subsonic/engine/list_generator.go | 150 ----------------------- server/subsonic/filter/filters.go | 95 ++++++++++++++ server/subsonic/helpers.go | 40 ++---- server/subsonic/wire_gen.go | 3 +- 7 files changed, 182 insertions(+), 307 deletions(-) create mode 100644 server/subsonic/filter/filters.go diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index 60ce21738..dbe860445 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -1,66 +1,71 @@ package subsonic import ( + "context" "errors" "net/http" "github.com/deluan/navidrome/log" + "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/server/subsonic/engine" + "github.com/deluan/navidrome/server/subsonic/filter" "github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/utils" ) type AlbumListController struct { + ds model.DataStore listGen engine.ListGenerator } -func NewAlbumListController(listGen engine.ListGenerator) *AlbumListController { +func NewAlbumListController(ds model.DataStore, listGen engine.ListGenerator) *AlbumListController { c := &AlbumListController{ + ds: ds, listGen: listGen, } return c } -func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, error) { +func (c *AlbumListController) getAlbumList(r *http.Request) (model.Albums, error) { typ, err := requiredParamString(r, "type", "Required string parameter 'type' is not present") if err != nil { return nil, err } - var filter engine.ListFilter + var opts filter.Options switch typ { case "newest": - filter = engine.ByNewest() + opts = filter.AlbumsByNewest() case "recent": - filter = engine.ByRecent() + opts = filter.AlbumsByRecent() case "random": - filter = engine.ByRandom() + opts = filter.AlbumsByRandom() case "alphabeticalByName": - filter = engine.ByName() + opts = filter.AlbumsByName() case "alphabeticalByArtist": - filter = engine.ByArtist() + opts = filter.AlbumsByArtist() case "frequent": - filter = engine.ByFrequent() + opts = filter.AlbumsByFrequent() case "starred": - filter = engine.ByStarred() + opts = filter.AlbumsByStarred() case "highest": - filter = engine.ByRating() + opts = filter.AlbumsByRating() case "byGenre": - filter = engine.ByGenre(utils.ParamString(r, "genre")) + opts = filter.AlbumsByGenre(utils.ParamString(r, "genre")) case "byYear": - filter = engine.ByYear(utils.ParamInt(r, "fromYear", 0), utils.ParamInt(r, "toYear", 0)) + opts = filter.AlbumsByYear(utils.ParamInt(r, "fromYear", 0), utils.ParamInt(r, "toYear", 0)) default: log.Error(r, "albumList type not implemented", "type", typ) - return nil, errors.New("Not implemented!") + return nil, errors.New("not implemented") } - offset := utils.ParamInt(r, "offset", 0) - size := utils.MinInt(utils.ParamInt(r, "size", 10), 500) + opts.Offset = utils.ParamInt(r, "offset", 0) + opts.Max = utils.MinInt(utils.ParamInt(r, "size", 10), 500) + albums, err := c.ds.Album(r.Context()).GetAll(model.QueryOptions(opts)) - albums, err := c.listGen.GetAlbums(r.Context(), offset, size, filter) if err != nil { log.Error(r, "Error retrieving albums", "error", err) - return nil, errors.New("Internal Error") + return nil, errors.New("internal Error") } return albums, nil @@ -73,7 +78,7 @@ func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Reques } response := newResponse() - response.AlbumList = &responses.AlbumList{Album: toChildren(r.Context(), albums)} + response.AlbumList = &responses.AlbumList{Album: childrenFromAlbums(r.Context(), albums)} return response, nil } @@ -84,37 +89,45 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque } response := newResponse() - response.AlbumList2 = &responses.AlbumList{Album: toAlbums(r.Context(), albums)} + response.AlbumList2 = &responses.AlbumList{Album: childrenFromAlbums(r.Context(), albums)} return response, nil } func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context()) + ctx := r.Context() + options := model.QueryOptions{Sort: "starred_at", Order: "desc"} + artists, err := c.ds.Artist(ctx).GetStarred(options) if err != nil { - log.Error(r, "Error retrieving starred media", "error", err) + log.Error(r, "Error retrieving starred artists", "error", err) + return nil, newError(responses.ErrorGeneric, "Internal Error") + } + albums, err := c.ds.Album(ctx).GetStarred(options) + if err != nil { + log.Error(r, "Error retrieving starred albums", "error", err) + return nil, newError(responses.ErrorGeneric, "Internal Error") + } + mediaFiles, err := c.ds.MediaFile(ctx).GetStarred(options) + if err != nil { + log.Error(r, "Error retrieving starred mediaFiles", "error", err) return nil, newError(responses.ErrorGeneric, "Internal Error") } response := newResponse() response.Starred = &responses.Starred{} - response.Starred.Artist = toArtists(artists) - response.Starred.Album = toChildren(r.Context(), albums) - response.Starred.Song = toChildren(r.Context(), mediaFiles) + response.Starred.Artist = toArtists(ctx, artists) + response.Starred.Album = childrenFromAlbums(r.Context(), albums) + response.Starred.Song = childrenFromMediaFiles(r.Context(), mediaFiles) return response, nil } func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context()) + resp, err := c.GetStarred(w, r) if err != nil { - log.Error(r, "Error retrieving starred media", "error", err) - return nil, newError(responses.ErrorGeneric, "Internal Error") + return nil, err } response := newResponse() - response.Starred2 = &responses.Starred{} - response.Starred2.Artist = toArtists(artists) - response.Starred2.Album = toAlbums(r.Context(), albums) - response.Starred2.Song = toChildren(r.Context(), mediaFiles) + response.Starred2 = resp.Starred return response, nil } @@ -144,7 +157,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ fromYear := utils.ParamInt(r, "fromYear", 0) toYear := utils.ParamInt(r, "toYear", 0) - songs, err := c.listGen.GetSongs(r.Context(), 0, size, engine.SongsByRandom(genre, fromYear, toYear)) + songs, err := c.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear)) if err != nil { log.Error(r, "Error retrieving random songs", "error", err) return nil, newError(responses.ErrorGeneric, "Internal Error") @@ -152,7 +165,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ response := newResponse() response.RandomSongs = &responses.Songs{} - response.RandomSongs.Songs = toChildren(r.Context(), songs) + response.RandomSongs.Songs = childrenFromMediaFiles(r.Context(), songs) return response, nil } @@ -161,7 +174,7 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500) genre := utils.ParamString(r, "genre") - songs, err := c.listGen.GetSongs(r.Context(), offset, count, engine.SongsByGenre(genre)) + songs, err := c.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre)) if err != nil { log.Error(r, "Error retrieving random songs", "error", err) return nil, newError(responses.ErrorGeneric, "Internal Error") @@ -169,6 +182,12 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req response := newResponse() response.SongsByGenre = &responses.Songs{} - response.SongsByGenre.Songs = toChildren(r.Context(), songs) + response.SongsByGenre.Songs = childrenFromMediaFiles(r.Context(), songs) return response, nil } + +func (c *AlbumListController) getSongs(ctx context.Context, offset, size int, opts filter.Options) (model.MediaFiles, error) { + opts.Offset = offset + opts.Max = size + return c.ds.MediaFile(ctx).GetAll(model.QueryOptions(opts)) +} diff --git a/server/subsonic/album_lists_test.go b/server/subsonic/album_lists_test.go index d97ddcfa2..8e4c6c8d7 100644 --- a/server/subsonic/album_lists_test.go +++ b/server/subsonic/album_lists_test.go @@ -2,55 +2,40 @@ package subsonic import ( "context" - "errors" "net/http/httptest" - "github.com/deluan/navidrome/server/subsonic/engine" + "github.com/deluan/navidrome/log" + "github.com/deluan/navidrome/model" + "github.com/deluan/navidrome/persistence" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -type fakeListGen struct { - engine.ListGenerator - data engine.Entries - err error - recvOffset int - recvSize int -} - -func (lg *fakeListGen) GetAlbums(ctx context.Context, offset int, size int, filter engine.ListFilter) (engine.Entries, error) { - if lg.err != nil { - return nil, lg.err - } - lg.recvOffset = offset - lg.recvSize = size - return lg.data, nil -} - var _ = Describe("AlbumListController", func() { var controller *AlbumListController - var listGen *fakeListGen + var ds model.DataStore + var mockRepo *persistence.MockAlbum var w *httptest.ResponseRecorder + ctx := log.NewContext(context.TODO()) BeforeEach(func() { - listGen = &fakeListGen{} - controller = NewAlbumListController(listGen) + ds = &persistence.MockDataStore{} + mockRepo = ds.Album(ctx).(*persistence.MockAlbum) + controller = NewAlbumListController(ds, nil) w = httptest.NewRecorder() }) Describe("GetAlbumList", func() { It("should return list of the type specified", func() { r := newGetRequest("type=newest", "offset=10", "size=20") - listGen.data = engine.Entries{ - {Id: "1"}, {Id: "2"}, - } + mockRepo.SetData(`[{"id": "1"},{"id": "2"}]`) resp, err := controller.GetAlbumList(w, r) Expect(err).To(BeNil()) Expect(resp.AlbumList.Album[0].Id).To(Equal("1")) Expect(resp.AlbumList.Album[1].Id).To(Equal("2")) - Expect(listGen.recvOffset).To(Equal(10)) - Expect(listGen.recvSize).To(Equal(20)) + Expect(mockRepo.Options.Offset).To(Equal(10)) + Expect(mockRepo.Options.Max).To(Equal(20)) }) It("should fail if missing type parameter", func() { @@ -61,28 +46,26 @@ var _ = Describe("AlbumListController", func() { }) It("should return error if call fails", func() { - listGen.err = errors.New("some issue") + mockRepo.SetError(true) r := newGetRequest("type=newest") _, err := controller.GetAlbumList(w, r) - Expect(err).To(MatchError("Internal Error")) + Expect(err).ToNot(BeNil()) }) }) Describe("GetAlbumList2", func() { It("should return list of the type specified", func() { r := newGetRequest("type=newest", "offset=10", "size=20") - listGen.data = engine.Entries{ - {Id: "1"}, {Id: "2"}, - } + mockRepo.SetData(`[{"id": "1"},{"id": "2"}]`) resp, err := controller.GetAlbumList2(w, r) Expect(err).To(BeNil()) Expect(resp.AlbumList2.Album[0].Id).To(Equal("1")) Expect(resp.AlbumList2.Album[1].Id).To(Equal("2")) - Expect(listGen.recvOffset).To(Equal(10)) - Expect(listGen.recvSize).To(Equal(20)) + Expect(mockRepo.Options.Offset).To(Equal(10)) + Expect(mockRepo.Options.Max).To(Equal(20)) }) It("should fail if missing type parameter", func() { @@ -93,12 +76,12 @@ var _ = Describe("AlbumListController", func() { }) It("should return error if call fails", func() { - listGen.err = errors.New("some issue") + mockRepo.SetError(true) r := newGetRequest("type=newest") _, err := controller.GetAlbumList2(w, r) - Expect(err).To(MatchError("Internal Error")) + Expect(err).ToNot(BeNil()) }) }) }) diff --git a/server/subsonic/engine/common.go b/server/subsonic/engine/common.go index 239adcb39..20e812176 100644 --- a/server/subsonic/engine/common.go +++ b/server/subsonic/engine/common.go @@ -45,43 +45,6 @@ type Entry struct { type Entries []Entry -func FromArtist(ar *model.Artist) Entry { - e := Entry{} - e.Id = ar.ID - e.Title = ar.Name - e.AlbumCount = ar.AlbumCount - e.IsDir = true - e.UserRating = ar.Rating - if ar.Starred { - e.Starred = ar.StarredAt - } - return e -} - -func FromAlbum(al *model.Album) Entry { - e := Entry{} - e.Id = al.ID - e.Title = al.Name - e.IsDir = true - e.Parent = al.AlbumArtistID - e.Album = al.Name - e.Year = al.MaxYear - e.Artist = al.AlbumArtist - e.Genre = al.Genre - e.CoverArt = al.CoverArtId - e.Created = al.CreatedAt - e.AlbumId = al.ID - e.ArtistId = al.AlbumArtistID - e.Duration = int(al.Duration) - e.SongCount = al.SongCount - if al.Starred { - e.Starred = al.StarredAt - } - e.PlayCount = int32(al.PlayCount) - e.UserRating = al.Rating - return e -} - func FromMediaFile(mf *model.MediaFile) Entry { e := Entry{} e.Id = mf.ID @@ -133,15 +96,6 @@ func realArtistName(mf *model.MediaFile) string { return mf.Artist } -func FromAlbums(albums model.Albums) Entries { - entries := make(Entries, len(albums)) - for i := range albums { - al := albums[i] - entries[i] = FromAlbum(&al) - } - return entries -} - func FromMediaFiles(mfs model.MediaFiles) Entries { entries := make(Entries, len(mfs)) for i := range mfs { @@ -150,12 +104,3 @@ func FromMediaFiles(mfs model.MediaFiles) Entries { } return entries } - -func FromArtists(ars model.Artists) Entries { - entries := make(Entries, len(ars)) - for i := range ars { - ar := ars[i] - entries[i] = FromArtist(&ar) - } - return entries -} diff --git a/server/subsonic/engine/list_generator.go b/server/subsonic/engine/list_generator.go index 60202959e..b263f3d2c 100644 --- a/server/subsonic/engine/list_generator.go +++ b/server/subsonic/engine/list_generator.go @@ -4,172 +4,22 @@ import ( "context" "time" - "github.com/Masterminds/squirrel" "github.com/deluan/navidrome/model" ) type ListGenerator interface { - GetAllStarred(ctx context.Context) (artists Entries, albums Entries, mediaFiles Entries, err error) GetNowPlaying(ctx context.Context) (Entries, error) - GetSongs(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) - GetAlbums(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) } func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator { return &listGenerator{ds, npRepo} } -type ListFilter model.QueryOptions - -func ByNewest() ListFilter { - return ListFilter{Sort: "createdAt", Order: "desc"} -} - -func ByRecent() ListFilter { - return ListFilter{Sort: "playDate", Order: "desc", Filters: squirrel.Gt{"play_date": time.Time{}}} -} - -func ByFrequent() ListFilter { - return ListFilter{Sort: "playCount", Order: "desc", Filters: squirrel.Gt{"play_count": 0}} -} - -func ByRandom() ListFilter { - return ListFilter{Sort: "random()"} -} - -func ByName() ListFilter { - return ListFilter{Sort: "name"} -} - -func ByArtist() ListFilter { - return ListFilter{Sort: "artist"} -} - -func ByStarred() ListFilter { - return ListFilter{Sort: "starred_at", Order: "desc", Filters: squirrel.Eq{"starred": true}} -} - -func ByRating() ListFilter { - return ListFilter{Sort: "Rating", Order: "desc", Filters: squirrel.Gt{"rating": 0}} -} - -func ByGenre(genre string) ListFilter { - return ListFilter{ - Sort: "genre asc, name asc", - Filters: squirrel.Eq{"genre": genre}, - } -} - -func ByYear(fromYear, toYear int) ListFilter { - sortOption := "max_year, name" - if fromYear > toYear { - fromYear, toYear = toYear, fromYear - sortOption = "max_year desc, name" - } - return ListFilter{ - Sort: sortOption, - Filters: squirrel.Or{ - squirrel.And{ - squirrel.GtOrEq{"min_year": fromYear}, - squirrel.LtOrEq{"min_year": toYear}, - }, - squirrel.And{ - squirrel.GtOrEq{"max_year": fromYear}, - squirrel.LtOrEq{"max_year": toYear}, - }, - }, - } -} - -func SongsByGenre(genre string) ListFilter { - return ListFilter{ - Sort: "genre asc, title asc", - Filters: squirrel.Eq{"genre": genre}, - } -} - -func SongsByRandom(genre string, fromYear, toYear int) ListFilter { - options := ListFilter{ - Sort: "random()", - } - ff := squirrel.And{} - if genre != "" { - ff = append(ff, squirrel.Eq{"genre": genre}) - } - if fromYear != 0 { - ff = append(ff, squirrel.GtOrEq{"year": fromYear}) - } - if toYear != 0 { - ff = append(ff, squirrel.LtOrEq{"year": toYear}) - } - options.Filters = ff - return options -} - type listGenerator struct { ds model.DataStore npRepo NowPlayingRepository } -func (g *listGenerator) GetSongs(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) { - qo := model.QueryOptions(filter) - qo.Offset = offset - qo.Max = size - mediaFiles, err := g.ds.MediaFile(ctx).GetAll(qo) - if err != nil { - return nil, err - } - - return FromMediaFiles(mediaFiles), nil -} - -func (g *listGenerator) GetAlbums(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) { - qo := model.QueryOptions(filter) - qo.Offset = offset - qo.Max = size - albums, err := g.ds.Album(ctx).GetAll(qo) - if err != nil { - return nil, err - } - - return FromAlbums(albums), nil -} - -func (g *listGenerator) GetStarred(ctx context.Context, offset int, size int) (Entries, error) { - qo := model.QueryOptions{Offset: offset, Max: size, Sort: "starred_at", Order: "desc"} - albums, err := g.ds.Album(ctx).GetStarred(qo) - if err != nil { - return nil, err - } - - return FromAlbums(albums), nil -} - -func (g *listGenerator) GetAllStarred(ctx context.Context) (artists Entries, albums Entries, mediaFiles Entries, err error) { - options := model.QueryOptions{Sort: "starred_at", Order: "desc"} - - ars, err := g.ds.Artist(ctx).GetStarred(options) - if err != nil { - return nil, nil, nil, err - } - - als, err := g.ds.Album(ctx).GetStarred(options) - if err != nil { - return nil, nil, nil, err - } - - mfs, err := g.ds.MediaFile(ctx).GetStarred(options) - if err != nil { - return nil, nil, nil, err - } - - artists = FromArtists(ars) - albums = FromAlbums(als) - mediaFiles = FromMediaFiles(mfs) - - return -} - func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) { npInfo, err := g.npRepo.GetAll() if err != nil { diff --git a/server/subsonic/filter/filters.go b/server/subsonic/filter/filters.go new file mode 100644 index 000000000..d3be40b47 --- /dev/null +++ b/server/subsonic/filter/filters.go @@ -0,0 +1,95 @@ +package filter + +import ( + "time" + + "github.com/Masterminds/squirrel" + "github.com/deluan/navidrome/model" +) + +type Options model.QueryOptions + +func AlbumsByNewest() Options { + return Options{Sort: "createdAt", Order: "desc"} +} + +func AlbumsByRecent() Options { + return Options{Sort: "playDate", Order: "desc", Filters: squirrel.Gt{"play_date": time.Time{}}} +} + +func AlbumsByFrequent() Options { + return Options{Sort: "playCount", Order: "desc", Filters: squirrel.Gt{"play_count": 0}} +} + +func AlbumsByRandom() Options { + return Options{Sort: "random()"} +} + +func AlbumsByName() Options { + return Options{Sort: "name"} +} + +func AlbumsByArtist() Options { + return Options{Sort: "artist"} +} + +func AlbumsByStarred() Options { + return Options{Sort: "starred_at", Order: "desc", Filters: squirrel.Eq{"starred": true}} +} + +func AlbumsByRating() Options { + return Options{Sort: "Rating", Order: "desc", Filters: squirrel.Gt{"rating": 0}} +} + +func AlbumsByGenre(genre string) Options { + return Options{ + Sort: "genre asc, name asc", + Filters: squirrel.Eq{"genre": genre}, + } +} + +func AlbumsByYear(fromYear, toYear int) Options { + sortOption := "max_year, name" + if fromYear > toYear { + fromYear, toYear = toYear, fromYear + sortOption = "max_year desc, name" + } + return Options{ + Sort: sortOption, + Filters: squirrel.Or{ + squirrel.And{ + squirrel.GtOrEq{"min_year": fromYear}, + squirrel.LtOrEq{"min_year": toYear}, + }, + squirrel.And{ + squirrel.GtOrEq{"max_year": fromYear}, + squirrel.LtOrEq{"max_year": toYear}, + }, + }, + } +} + +func SongsByGenre(genre string) Options { + return Options{ + Sort: "genre asc, title asc", + Filters: squirrel.Eq{"genre": genre}, + } +} + +func SongsByRandom(genre string, fromYear, toYear int) Options { + options := Options{ + Sort: "random()", + } + ff := squirrel.And{} + if genre != "" { + ff = append(ff, squirrel.Eq{"genre": genre}) + } + if fromYear != 0 { + ff = append(ff, squirrel.GtOrEq{"year": fromYear}) + } + if toYear != 0 { + ff = append(ff, squirrel.LtOrEq{"year": toYear}) + } + options.Filters = ff + return options +} diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index a3d69eb5d..403cf5a87 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -64,38 +64,20 @@ func (e SubsonicError) Error() string { return msg } -func toAlbums(ctx context.Context, entries engine.Entries) []responses.Child { - children := make([]responses.Child, len(entries)) - for i, entry := range entries { - children[i] = toAlbum(ctx, entry) - } - return children -} - -func toAlbum(ctx context.Context, entry engine.Entry) responses.Child { - album := toChild(ctx, entry) - album.Name = album.Title - album.Title = "" - album.Parent = "" - album.Album = "" - album.AlbumId = "" - return album -} - -func toArtists(entries engine.Entries) []responses.Artist { - artists := make([]responses.Artist, len(entries)) - for i, entry := range entries { - artists[i] = responses.Artist{ - Id: entry.Id, - Name: entry.Title, - AlbumCount: entry.AlbumCount, - UserRating: entry.UserRating, +func toArtists(ctx context.Context, artists model.Artists) []responses.Artist { + as := make([]responses.Artist, len(artists)) + for i, artist := range artists { + as[i] = responses.Artist{ + Id: artist.ID, + Name: artist.Name, + AlbumCount: artist.AlbumCount, + UserRating: artist.Rating, } - if !entry.Starred.IsZero() { - artists[i].Starred = &entry.Starred + if artist.Starred { + as[i].Starred = &artist.StarredAt } } - return artists + return as } func toChildren(ctx context.Context, entries engine.Entries) []responses.Child { diff --git a/server/subsonic/wire_gen.go b/server/subsonic/wire_gen.go index 1eca7a3b2..83c06d591 100644 --- a/server/subsonic/wire_gen.go +++ b/server/subsonic/wire_gen.go @@ -25,8 +25,9 @@ func initBrowsingController(router *Router) *BrowsingController { } func initAlbumListController(router *Router) *AlbumListController { + dataStore := router.DataStore listGenerator := router.ListGenerator - albumListController := NewAlbumListController(listGenerator) + albumListController := NewAlbumListController(dataStore, listGenerator) return albumListController }