diff --git a/.gitignore b/.gitignore index 4c49bd7..92819e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .env +gomusic.db diff --git a/databse.go b/databse.go new file mode 100644 index 0000000..22ebb8a --- /dev/null +++ b/databse.go @@ -0,0 +1,39 @@ +package main + +import ( + "gorm.io/gorm" + "gorm.io/driver/sqlite" +) + +type ArtistInfo struct { + gorm.Model + Name string + Popularity string + SpotifyID string + ArtistGenre ArtistGenre +} + +// Stores an association between several Genres and an individual artist +type ArtistGenre struct { + gorm.Model + Genres []Genre + ArtistInfoID uint +} + +type Genre struct { + gorm.Model + Genre string + ArtistGenreID uint +} + +func setupDatabase() *gorm.DB { + db, err := gorm.Open(sqlite.Open("gomusic.db"), &gorm.Config{}) + if err != nil { + panic("Failed to open database") + } + + // Run model migrations here to keep DB consistent + db.AutoMigrate(&ArtistInfo{}, &ArtistGenre{}, &Genre{}) + + return db +} diff --git a/go.mod b/go.mod index eb043e6..25de27d 100644 --- a/go.mod +++ b/go.mod @@ -2,25 +2,33 @@ module git.repo.cafe/froge/gomusic go 1.23.5 +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/stretchr/testify v1.10.0 +) + require ( github.com/bytedance/sonic v1.12.8 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/cloudwego/base64x v0.1.5 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.0.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.24.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.14.0 // indirect @@ -30,4 +38,6 @@ require ( golang.org/x/text v0.22.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/sqlite v1.5.7 // indirect + gorm.io/gorm v1.25.12 // indirect ) diff --git a/go.sum b/go.sum index d60e044..e79e143 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,9 @@ github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wio github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= @@ -15,6 +15,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -23,7 +25,13 @@ github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -34,6 +42,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -41,6 +51,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -52,6 +63,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -68,11 +80,17 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go index 9cabfdb..e4735d9 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,19 @@ import ( var spotifyClientID = os.Getenv("SPOTIFY_ID") var spotifyClientSecret = os.Getenv("SPOTIFY_SECRET") -// DB related setup -var db = make(map[string]string) +// Make sure the DB is in a good state to launch +var db = setupDatabase() + +func setupRouter() *gin.Engine { + var r *gin.Engine = gin.Default() + + // Add middleware to handle spotify auth for us + r.Use(spotifyAuth(spotifyClientID, spotifyClientSecret)) + + r.GET("/ping", ping) + r.GET("/artists/:artistID", getArtistByID) + return r +} func main() { // If the auth/ID variables are empty something is probably misconfigured @@ -21,15 +32,8 @@ func main() { if spotifyClientSecret == "" { slog.Warn("[GOMUSIC] No Spotify secret configured in 'SPOTIFY_SECRET' environment variable") } - - // API server setup - var r *gin.Engine = gin.Default() - - // Add middleware to handle spotify auth for us - r.Use(spotifyAuth(spotifyClientID, spotifyClientSecret)) - - r.GET("/ping", ping) - r.GET("/artists/:artistID", getArtistByID) - + + // Router and server setup + r := setupRouter() r.Run(":8080") } diff --git a/routes.go b/routes.go index c0744dc..8747169 100644 --- a/routes.go +++ b/routes.go @@ -9,14 +9,14 @@ import ( "io" ) -type ArtistInfo struct { - ID string - Name string - Popularity int - Genres []string +type SpotifyResponse struct { + ID string + Name string + Popularity int + Genres []string } -// Define our route functions here + func ping(c *gin.Context) { c.String(http.StatusOK, "pong") } @@ -26,7 +26,7 @@ func getArtistByID(c *gin.Context) { spotifyAuthToken := c.GetString("spotifyAuthToken") if artistID == "" || spotifyAuthToken == "Bearer " { - c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find required parameters and/or required authentication tokens"}) + c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find required parameters and/or required authentication tokens"}) return } @@ -34,27 +34,27 @@ func getArtistByID(c *gin.Context) { artistEndpoint := fmt.Sprintf("https://api.spotify.com/v1/artists/%s", artistID) req, err := http.NewRequest("GET", artistEndpoint, nil) if err != nil { - slog.Error("[GOMUSIC] Failed to build HTTP request", "Error", err) + slog.Error("[GOMUSIC] Failed to build HTTP request", "Error", err) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request latest spotify data"}) return } req.Header.Add("Authorization", spotifyAuthToken) - // Send request and save the response into DB + // Send off request resp, err := http.DefaultClient.Do(req) if err != nil { - slog.Error("[GOMUSIC] Failed to get artist data from spotify API", "Error", err) + slog.Error("[GOMUSIC] Failed to get artist data from spotify API", "Error", err) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request latest spotify data"}) return } else if resp.StatusCode != 200 { - slog.Error("[GOMUSIC] Failed to get artist data from spotify API", "Error", resp.Status) + slog.Error("[GOMUSIC] Failed to get artist data from spotify API", "Error", resp.Status) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request latest spotify data"}) return } respData, err := io.ReadAll(resp.Body) if err != nil { - slog.Error("[GOMUSIC] Failed to read response data from spotify API", "Error", resp.Status) + slog.Error("[GOMUSIC] Failed to read response data from spotify API", "Error", resp.Status) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request latest spotify data"}) return @@ -63,17 +63,14 @@ func getArtistByID(c *gin.Context) { // Close this immediately since it's unused now resp.Body.Close() - var artistInfo ArtistInfo - err = json.Unmarshal(respData, &artistInfo) + var spotifyResponse SpotifyResponse + err = json.Unmarshal(respData, &spotifyResponse) if err != nil { slog.Error("[GOMUSIC] Failed to read response body data from spotify", "Error", err) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request latest spotify data"}) return } - // Send back our response - c.JSON(http.StatusOK, artistInfo) - // Update DB here //value, ok := db[artistID] //if ok { @@ -81,4 +78,7 @@ func getArtistByID(c *gin.Context) { //} else { // c.JSON(http.StatusOK, gin.H{"artistID": artistID, "status": "no value"}) //} + + // Send back our response data + c.JSON(http.StatusOK, spotifyResponse) } diff --git a/routes_test.go b/routes_test.go new file mode 100644 index 0000000..ee4de8f --- /dev/null +++ b/routes_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} + +func TestGetArtistByIDRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/artists/0TnOYISbd1XYRBk9myaseg", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, `{"ID":"0TnOYISbd1XYRBk9myaseg","Name":"Pitbull","Popularity":83,"Genres":[]}`, w.Body.String()) +}