From 777231ea7907638b28020b63c2b6dafbf66453a5 Mon Sep 17 00:00:00 2001
From: Deluan <deluan@deluan.com>
Date: Sat, 28 Mar 2020 19:22:55 -0400
Subject: [PATCH] feat: expose album, song and artist annotations in the
 RESTful API

---
 model/album.go         | 10 +++++-----
 model/annotation.go    |  4 ++++
 model/artist.go        | 10 +++++-----
 model/mediafile.go     | 10 +++++-----
 persistence/helpers.go |  6 +++++-
 utils/strings.go       |  9 +++++++++
 utils/strings_test.go  | 14 ++++++++++++++
 7 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/model/album.go b/model/album.go
index 320c4df00..37761e6ce 100644
--- a/model/album.go
+++ b/model/album.go
@@ -22,11 +22,11 @@ type Album struct {
 	UpdatedAt     time.Time `json:"updatedAt"`
 
 	// Annotations
-	PlayCount int       `json:"-"   orm:"-"`
-	PlayDate  time.Time `json:"-"   orm:"-"`
-	Rating    int       `json:"-"   orm:"-"`
-	Starred   bool      `json:"-"   orm:"-"`
-	StarredAt time.Time `json:"-"   orm:"-"`
+	PlayCount int       `json:"playCount"   orm:"-"`
+	PlayDate  time.Time `json:"playDate"    orm:"-"`
+	Rating    int       `json:"rating"      orm:"-"`
+	Starred   bool      `json:"starred"     orm:"-"`
+	StarredAt time.Time `json:"starredAt"   orm:"-"`
 }
 
 type Albums []Album
diff --git a/model/annotation.go b/model/annotation.go
index 2293934da..505aefd8e 100644
--- a/model/annotation.go
+++ b/model/annotation.go
@@ -7,3 +7,7 @@ type AnnotatedRepository interface {
 	SetStar(starred bool, itemIDs ...string) error
 	SetRating(rating int, itemID string) error
 }
+
+// While I can't find a better way to make these fields optional in the models, I keep this list here
+// to be used in other packages
+var AnnotationFields = []string{"playCount", "playDate", "rating", "starred", "starredAt"}
diff --git a/model/artist.go b/model/artist.go
index b9171447b..a771e12dc 100644
--- a/model/artist.go
+++ b/model/artist.go
@@ -9,11 +9,11 @@ type Artist struct {
 	FullText   string `json:"fullText"`
 
 	// Annotations
-	PlayCount int       `json:"-"   orm:"-"`
-	PlayDate  time.Time `json:"-"   orm:"-"`
-	Rating    int       `json:"-"   orm:"-"`
-	Starred   bool      `json:"-"   orm:"-"`
-	StarredAt time.Time `json:"-"   orm:"-"`
+	PlayCount int       `json:"playCount"   orm:"-"`
+	PlayDate  time.Time `json:"playDate"    orm:"-"`
+	Rating    int       `json:"rating"      orm:"-"`
+	Starred   bool      `json:"starred"     orm:"-"`
+	StarredAt time.Time `json:"starredAt"   orm:"-"`
 }
 
 type Artists []Artist
diff --git a/model/mediafile.go b/model/mediafile.go
index 5b8e7907d..cbed33b8b 100644
--- a/model/mediafile.go
+++ b/model/mediafile.go
@@ -30,11 +30,11 @@ type MediaFile struct {
 	UpdatedAt     time.Time `json:"updatedAt"`
 
 	// Annotations
-	PlayCount int       `json:"-"   orm:"-"`
-	PlayDate  time.Time `json:"-"   orm:"-"`
-	Rating    int       `json:"-"   orm:"-"`
-	Starred   bool      `json:"-"   orm:"-"`
-	StarredAt time.Time `json:"-"   orm:"-"`
+	PlayCount int       `json:"playCount"   orm:"-"`
+	PlayDate  time.Time `json:"playDate"    orm:"-"`
+	Rating    int       `json:"rating"      orm:"-"`
+	Starred   bool      `json:"starred"     orm:"-"`
+	StarredAt time.Time `json:"starredAt"   orm:"-"`
 }
 
 func (mf *MediaFile) ContentType() string {
diff --git a/persistence/helpers.go b/persistence/helpers.go
index e1b726692..7746b36ce 100644
--- a/persistence/helpers.go
+++ b/persistence/helpers.go
@@ -7,6 +7,8 @@ import (
 	"strings"
 
 	"github.com/Masterminds/squirrel"
+	"github.com/deluan/navidrome/model"
+	"github.com/deluan/navidrome/utils"
 )
 
 func toSqlArgs(rec interface{}) (map[string]interface{}, error) {
@@ -21,7 +23,9 @@ func toSqlArgs(rec interface{}) (map[string]interface{}, error) {
 	err = json.Unmarshal(b, &m)
 	r := make(map[string]interface{}, len(m))
 	for f, v := range m {
-		r[toSnakeCase(f)] = v
+		if !utils.StringInSlice(f, model.AnnotationFields) {
+			r[toSnakeCase(f)] = v
+		}
 	}
 	return r, err
 }
diff --git a/utils/strings.go b/utils/strings.go
index 1611a19db..bc05a88ea 100644
--- a/utils/strings.go
+++ b/utils/strings.go
@@ -16,3 +16,12 @@ func NoArticle(name string) string {
 	}
 	return name
 }
+
+func StringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
diff --git a/utils/strings_test.go b/utils/strings_test.go
index d5cde3049..fda705d4d 100644
--- a/utils/strings_test.go
+++ b/utils/strings_test.go
@@ -34,4 +34,18 @@ var _ = Describe("Strings", func() {
 			})
 		})
 	})
+
+	Describe("StringInSlice", func() {
+		It("returns false if slice is empty", func() {
+			Expect(StringInSlice("test", nil)).To(BeFalse())
+		})
+
+		It("returns false if string is not found in slice", func() {
+			Expect(StringInSlice("aaa", []string{"bbb", "ccc"})).To(BeFalse())
+		})
+
+		It("returns true if string is found in slice", func() {
+			Expect(StringInSlice("bbb", []string{"bbb", "aaa", "ccc"})).To(BeTrue())
+		})
+	})
 })