Ver código fonte

feat: new route to fetch multiple song info at once

Fela Maslen 5 anos atrás
pai
commit
9c7fe810fb

+ 11 - 14
gmus-backend/pkg/repository/songs.go

@@ -1,8 +1,6 @@
 package repository
 
 import (
-	"errors"
-
 	"github.com/felamaslen/gmus-backend/pkg/read"
 	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
@@ -10,12 +8,17 @@ import (
 
 const BATCH_SIZE = 100
 
-func SelectSong(db *sqlx.DB, id int) (song *read.Song, err error) {
-	var songs []*read.Song
+func SelectSong(db *sqlx.DB, ids []int) (songs *[]*read.Song, err error) {
+	songs = &[]*read.Song{}
+	var idsArray pq.Int64Array
+	for _, id := range ids {
+		idsArray = append(idsArray, int64(id))
+	}
 
-	err = db.Select(&songs, `
+	err = db.Select(songs, `
   select
-    track_number
+    id
+    ,track_number
     ,title
     ,artist
     ,album
@@ -24,14 +27,8 @@ func SelectSong(db *sqlx.DB, id int) (song *read.Song, err error) {
     ,base_path
     ,relative_path
   from songs
-  where id = $1
-  `, int64(id))
-
-	if len(songs) == 0 {
-		err = errors.New("No such ID")
-	} else {
-		song = songs[0]
-	}
+  where id = ANY($1)
+  `, idsArray)
 
 	return
 }

+ 59 - 6
gmus-backend/pkg/repository/songs_test.go

@@ -19,6 +19,7 @@ var _ = Describe("songs repository", func() {
 
 	Describe("SelectSong", func() {
 		var id int64
+		var id2 int64
 
 		BeforeEach(func() {
 			db.QueryRowx(
@@ -36,16 +37,34 @@ var _ = Describe("songs repository", func() {
 				"/path/to",
 				"file.ogg",
 			).Scan(&id)
+
+			db.QueryRowx(
+				`
+	insert into songs (track_number, title, artist, album, duration, modified_date, base_path, relative_path)
+	values ($1, $2, $3, $4, $5, $6, $7, $8)
+	returning id
+	`,
+				13,
+				"Track 1",
+				"Untitled Artist",
+				"Some album",
+				218,
+				1993,
+				"/path/different",
+				"other.ogg",
+			).Scan(&id2)
 		})
 
 		It("should retrieve a song from the database", func() {
 			Expect(id).NotTo(BeZero())
 
-			result, err := repository.SelectSong(db, int(id))
+			result, err := repository.SelectSong(db, []int{int(id)})
 
 			Expect(err).To(BeNil())
 
-			Expect(result).To(Equal(&read.Song{
+			Expect(*result).To(HaveLen(1))
+			Expect((*result)[0]).To(Equal(&read.Song{
+				Id:           int(id),
 				TrackNumber:  7,
 				Title:        "Hey Jude",
 				Artist:       "The Beatles",
@@ -57,12 +76,46 @@ var _ = Describe("songs repository", func() {
 			}))
 		})
 
+		It("should retrieve multiple songs from the database", func() {
+			Expect(id).NotTo(BeZero())
+			Expect(id2).NotTo(BeZero())
+
+			result, err := repository.SelectSong(db, []int{int(id), int(id2)})
+
+			Expect(err).To(BeNil())
+
+			Expect(*result).To(HaveLen(2))
+			Expect((*result)[0]).To(Equal(&read.Song{
+				Id:           int(id),
+				TrackNumber:  7,
+				Title:        "Hey Jude",
+				Artist:       "The Beatles",
+				Album:        "",
+				Duration:     431,
+				BasePath:     "/path/to",
+				RelativePath: "file.ogg",
+				ModifiedDate: 8876,
+			}))
+
+			Expect((*result)[1]).To(Equal(&read.Song{
+				Id:           int(id2),
+				TrackNumber:  13,
+				Title:        "Track 1",
+				Artist:       "Untitled Artist",
+				Album:        "Some album",
+				Duration:     218,
+				BasePath:     "/path/different",
+				RelativePath: "other.ogg",
+				ModifiedDate: 1993,
+			}))
+		})
+
 		Context("when the song does not exist", func() {
-			It("should return an error", func() {
-				result, err := repository.SelectSong(db, 88113)
+			It("should return an empty array", func() {
+				result, err := repository.SelectSong(db, []int{88113})
 
-				Expect(err).To(MatchError("No such ID"))
-				Expect(result).To(BeNil())
+				Expect(err).To(BeNil())
+				Expect(*result).To(HaveLen(0))
 			})
 		})
 	})

+ 54 - 5
gmus-backend/pkg/server/fetch.go

@@ -108,14 +108,16 @@ func routeFetchSongInfo(l *logger.Logger, rdb redis.Cmdable, w http.ResponseWrit
 
 	db := database.GetConnection()
 
-	song, err := repository.SelectSong(db, id)
+	songs, err := repository.SelectSong(db, []int{id})
 	if err != nil {
-		if err.Error() == "No such ID" {
-			http.Error(w, "Song not found", http.StatusNotFound)
-			return nil
-		}
 		return err
 	}
+	if len(*songs) == 0 {
+		http.Error(w, "Song not found", http.StatusNotFound)
+		return nil
+	}
+
+	song := (*songs)[0]
 
 	response, err := json.Marshal(read.SongExternal{
 		Id:          id,
@@ -133,6 +135,53 @@ func routeFetchSongInfo(l *logger.Logger, rdb redis.Cmdable, w http.ResponseWrit
 	return nil
 }
 
+func routeFetchMultiSongInfo(l *logger.Logger, rdb redis.Cmdable, w http.ResponseWriter, r *http.Request) error {
+	idsArray := r.URL.Query()["ids"]
+	if len(idsArray) == 0 {
+		http.Error(w, "Must provide valid list of IDs", http.StatusBadRequest)
+		return nil
+	}
+
+	var ids []int
+	for _, id := range idsArray {
+		idInt, err := strconv.Atoi(id)
+		if err != nil {
+			http.Error(w, "All IDs must be numeric", http.StatusBadRequest)
+			return nil
+		}
+		if idInt < 1 {
+			http.Error(w, "All IDs must be positive integers", http.StatusBadRequest)
+			return nil
+		}
+		ids = append(ids, idInt)
+	}
+
+	songs, err := repository.SelectSong(database.GetConnection(), ids)
+	if err != nil {
+		return err
+	}
+
+	songsArray := []read.SongExternal{}
+	for _, song := range *songs {
+		songsArray = append(songsArray, read.SongExternal{
+			Id:          song.Id,
+			TrackNumber: song.TrackNumber,
+			Title:       song.Title,
+			Artist:      song.Artist,
+			Album:       song.Album,
+			Duration:    song.Duration,
+		})
+	}
+
+	response, err := json.Marshal(songsArray)
+	if err != nil {
+		return err
+	}
+
+	w.Write(response)
+	return nil
+}
+
 type NullResponse struct {
 	Id int `json:"id"`
 }

+ 1 - 0
gmus-backend/pkg/server/server.go

@@ -30,6 +30,7 @@ func StartServer() {
 	router.Path("/songs").Methods("GET").HandlerFunc(routeHandler(l, rdb, routeFetchSongs))
 
 	router.Path("/song-info").Methods("GET").HandlerFunc(routeHandler(l, rdb, routeFetchSongInfo))
+	router.Path("/multi-song-info").Methods("GET").HandlerFunc(routeHandler(l, rdb, routeFetchMultiSongInfo))
 
 	router.Path("/next-song").Methods("GET").HandlerFunc(routeHandler(l, rdb, routeFetchNextSong))
 	router.Path("/prev-song").Methods("GET").HandlerFunc(routeHandler(l, rdb, routeFetchPrevSong))

+ 8 - 7
gmus-backend/pkg/server/stream.go

@@ -24,16 +24,17 @@ func streamSong(l *logger.Logger, rdb redis.Cmdable, w http.ResponseWriter, r *h
 
 	db := database.GetConnection()
 
-	song, err := repository.SelectSong(db, songId)
+	songs, err := repository.SelectSong(db, []int{songId})
 	if err != nil {
-		if err.Error() == "No such ID" {
-			w.WriteHeader(404)
-			w.Write([]byte("No such song"))
-			return nil
-		}
-
 		return err
 	}
+	if len(*songs) == 0 {
+		w.WriteHeader(404)
+		w.Write([]byte("No such song"))
+		return nil
+	}
+
+	song := (*songs)[0]
 
 	fullFilePath := fmt.Sprintf("%s/%s", song.BasePath, song.RelativePath)