From d56b21a3d16055f166c144188a7776a37f1ea8c8 Mon Sep 17 00:00:00 2001 From: froge Date: Fri, 14 Feb 2025 05:54:45 +1000 Subject: [PATCH] Cleanup API responses and add better comments --- auth.go | 14 ++------------ database.go | 45 ++++++++++++++++++++++++++++++++++++++++++--- routes.go | 14 +++++++------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/auth.go b/auth.go index f1a491f..be66ea2 100644 --- a/auth.go +++ b/auth.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" - "io" "log/slog" "net/http" "strings" @@ -48,21 +47,14 @@ func getSpotifyToken(clientID string, clientSecret string) (SpotifyToken, error) slog.Error("[GOMUSIC] Failed to authenticate with spotify", "Error", resp.Status) return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %s", resp.Status) } - respData, err := io.ReadAll(resp.Body) - if err != nil { - slog.Error("[GOMUSIC] Failed to read response body data from spotify", "Error", err) - return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify") - } - // Close this immediately since it's unused now - resp.Body.Close() var spotifyToken SpotifyToken - err = json.Unmarshal(respData, &spotifyToken) + err = json.NewDecoder(resp.Body).Decode(&spotifyToken) if err != nil { slog.Error("[GOMUSIC] Failed to parse spotify authentication response", "Error", err) return SpotifyToken{}, fmt.Errorf("Failed to parse spotify JSON response: %w", err) } - + resp.Body.Close() return spotifyToken, nil } @@ -93,8 +85,6 @@ func spotifyAuth(clientID string, clientSecret string) gin.HandlerFunc { // Pass the authorization header into context c.Set("spotifyAuthToken", fmt.Sprintf("Bearer %s", spotifyToken.AccessToken)) - - // Process the request c.Next() } } diff --git a/database.go b/database.go index 31f4da2..1144273 100644 --- a/database.go +++ b/database.go @@ -3,8 +3,10 @@ package main import ( "fmt" "gorm.io/driver/sqlite" + "encoding/json" "gorm.io/gorm" "log/slog" + "time" ) type ArtistProfile struct { @@ -22,14 +24,13 @@ type Genre struct { func setupTestDatabase(name string) *gorm.DB { slog.Info("[GOMUSIC] Setting up new test database in memory") - // Open a named DB instance so each test has a clean environment + // Open a named DB instance in memory so each test has a clean environment dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared", name) db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{}) if err != nil { panic("Failed to open database") } - // Run model migrations here to keep DB consistent db.AutoMigrate(&ArtistProfile{}, &Genre{}) return db } @@ -41,7 +42,45 @@ func setupDatabase() *gorm.DB { panic("Failed to open database") } - // Run model migrations here to keep DB consistent db.AutoMigrate(&ArtistProfile{}, &Genre{}) return db } + +// Some functions to control JSON marshalling of DB structs +func (p *ArtistProfile) MarshalJSON() ([]byte, error) { + type CleanedProfile struct { + UpdatedAt time.Time + SpotifyID string + Name string + Popularity int + Genres []string + } + + artistGenres := make([]string, 0, len(p.Genres)) + for _, val := range p.Genres { + artistGenres = append(artistGenres, val.Name) + } + + t := CleanedProfile{ + UpdatedAt: p.UpdatedAt, + SpotifyID: p.SpotifyID, + Name: p.Name, + Popularity: p.Popularity, + Genres: artistGenres, + } + return json.Marshal(&t) +} + +func (g *Genre) MarshalJSON() ([]byte, error) { + type CleanedGenre struct { + Name string + CreatedAt time.Time + } + + t := CleanedGenre{ + Name: g.Name, + CreatedAt: g.CreatedAt, + } + + return json.Marshal(&t) +} diff --git a/routes.go b/routes.go index 6642a41..ef2411f 100644 --- a/routes.go +++ b/routes.go @@ -23,6 +23,8 @@ func (env *Env) getArtistByID(c *gin.Context) { spotifyResponse, err := getSpotifyArtistData(artistID, spotifyAuthToken) if err != nil { + // cast the error back into our custom type + // so we can get context on why things failed statusCode := err.(*ResponseError).StatusCode switch statusCode { case 404: @@ -43,16 +45,13 @@ func (env *Env) getArtistByID(c *gin.Context) { genreList = append(genreList, Genre{Name: val}) } - artistProfile := ArtistProfile{ + artistProfile := &ArtistProfile{ SpotifyID: spotifyResponse.ID, Name: spotifyResponse.Name, Popularity: spotifyResponse.Popularity, Genres: genreList, } - // Create new record - // Otherwise update values when artist with this SpotifyID already exists - // Basically upsert dbResult := env.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "spotify_id"}}, UpdateAll: true, @@ -62,8 +61,7 @@ func (env *Env) getArtistByID(c *gin.Context) { slog.Error("[GOMUSIC] Failed to store response in local database", "Error", err) } - // Send back our response data - c.JSON(http.StatusOK, spotifyResponse) + c.JSON(http.StatusOK, artistProfile) } func (env *Env) getArtistByName(c *gin.Context) { @@ -74,12 +72,14 @@ func (env *Env) getArtistByName(c *gin.Context) { } // Lookup this name in the DB and return any ArtistProfile objects + // This is case insensitive on sqlite var artistProfiles []ArtistProfile - dbResult := env.db.Where("name LIKE ?", fmt.Sprintf("%%%s%%", artistName)).Find(&artistProfiles) + dbResult := env.db.Where("name LIKE ?", fmt.Sprintf("%%%s%%", artistName)).Preload("Genres").Find(&artistProfiles) if dbResult.Error != nil { slog.Error("[GOMUSIC] Failed to query local database for artist name", "Name", artistName) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to lookup name"}) return } + c.JSON(http.StatusOK, artistProfiles) }