Run go fmt
This commit is contained in:
parent
47d45625bc
commit
72fef2e3b2
7 changed files with 122 additions and 122 deletions
48
auth.go
48
auth.go
|
@ -1,27 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
"fmt"
|
||||
"io"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Used to unmarshal response data into type safe struct
|
||||
type SpotifyToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// Makes a HTTP request to grab a short lived access token from Spotify
|
||||
func getSpotifyToken(clientID string, clientSecret string) (SpotifyToken, error) {
|
||||
|
||||
|
||||
// See spotify API docs for details on parameters/urls required here
|
||||
// https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow
|
||||
spotifyAuthURL := "https://accounts.spotify.com/api/token"
|
||||
|
@ -43,7 +43,7 @@ func getSpotifyToken(clientID string, clientSecret string) (SpotifyToken, error)
|
|||
if err != nil {
|
||||
slog.Error("[GOMUSIC] Failed to send authentication request to spotify", "Error", err)
|
||||
return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %w", err)
|
||||
|
||||
|
||||
} else if resp.StatusCode != 200 {
|
||||
slog.Error("[GOMUSIC] Failed to authenticate with spotify", "Error", resp.Status)
|
||||
return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %s", resp.Status)
|
||||
|
@ -56,13 +56,13 @@ func getSpotifyToken(clientID string, clientSecret string) (SpotifyToken, error)
|
|||
// Close this immediately since it's unused now
|
||||
resp.Body.Close()
|
||||
|
||||
var spotifyToken SpotifyToken
|
||||
var spotifyToken SpotifyToken
|
||||
err = json.Unmarshal(respData, &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)
|
||||
}
|
||||
|
||||
|
||||
return spotifyToken, nil
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ func spotifyAuth(clientID string, clientSecret string) gin.HandlerFunc {
|
|||
// Short lived spotify access tokens during execution
|
||||
spotifyToken, err := getSpotifyToken(clientID, clientSecret)
|
||||
if err != nil {
|
||||
slog.Error("[GOMUSIC] Failed to get spotify access token", "Error", err)
|
||||
slog.Error("[GOMUSIC] Failed to get spotify access token", "Error", err)
|
||||
}
|
||||
|
||||
// Calculate the future expiry time
|
||||
|
@ -81,14 +81,14 @@ func spotifyAuth(clientID string, clientSecret string) gin.HandlerFunc {
|
|||
|
||||
return func(c *gin.Context) {
|
||||
if time.Now().After(expireTime) {
|
||||
// Token expired so we grab a new one
|
||||
slog.Info("[GOMUSIC] Replacing expired Spotify access token")
|
||||
spotifyToken, err = getSpotifyToken(clientID, clientSecret)
|
||||
if err != nil {
|
||||
slog.Error("[GOMUSIC] Failed to get new spotify access token", "Error", err)
|
||||
}
|
||||
expDuration := time.Duration(spotifyToken.ExpiresIn) * time.Second
|
||||
expireTime = time.Now().Add(expDuration)
|
||||
// Token expired so we grab a new one
|
||||
slog.Info("[GOMUSIC] Replacing expired Spotify access token")
|
||||
spotifyToken, err = getSpotifyToken(clientID, clientSecret)
|
||||
if err != nil {
|
||||
slog.Error("[GOMUSIC] Failed to get new spotify access token", "Error", err)
|
||||
}
|
||||
expDuration := time.Duration(spotifyToken.ExpiresIn) * time.Second
|
||||
expireTime = time.Now().Add(expDuration)
|
||||
}
|
||||
|
||||
// Pass the authorization header into context
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestgetSpotifyAuthURL(t *testing.T) {
|
||||
|
|
40
database.go
40
database.go
|
@ -1,26 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"log/slog"
|
||||
"fmt"
|
||||
)
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type ArtistProfile struct {
|
||||
gorm.Model
|
||||
SpotifyID string `gorm:"unique"`
|
||||
Name string
|
||||
Popularity int
|
||||
Genres []Genre `gorm:"many2many:artist_genres;"`
|
||||
gorm.Model
|
||||
SpotifyID string `gorm:"unique"`
|
||||
Name string
|
||||
Popularity int
|
||||
Genres []Genre `gorm:"many2many:artist_genres;"`
|
||||
}
|
||||
|
||||
type Genre struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"unique"`
|
||||
gorm.Model
|
||||
Name string `gorm:"unique"`
|
||||
}
|
||||
|
||||
func setupTestDatabase(name string) *gorm.DB {
|
||||
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
|
||||
dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared", name)
|
||||
|
@ -30,18 +30,18 @@ func setupTestDatabase(name string) *gorm.DB {
|
|||
}
|
||||
|
||||
// Run model migrations here to keep DB consistent
|
||||
db.AutoMigrate(&ArtistProfile{}, &Genre{})
|
||||
db.AutoMigrate(&ArtistProfile{}, &Genre{})
|
||||
return db
|
||||
}
|
||||
|
||||
func setupDatabase() *gorm.DB {
|
||||
func setupDatabase() *gorm.DB {
|
||||
slog.Info("[GOMUSIC] Setting up database and running auto migrations")
|
||||
db, err := gorm.Open(sqlite.Open("gomusic.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("Failed to open database")
|
||||
}
|
||||
if err != nil {
|
||||
panic("Failed to open database")
|
||||
}
|
||||
|
||||
// Run model migrations here to keep DB consistent
|
||||
db.AutoMigrate(&ArtistProfile{}, &Genre{})
|
||||
return db
|
||||
// Run model migrations here to keep DB consistent
|
||||
db.AutoMigrate(&ArtistProfile{}, &Genre{})
|
||||
return db
|
||||
}
|
||||
|
|
40
main.go
40
main.go
|
@ -1,11 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Environment type is used with route methods
|
||||
// So that we can easily inject/contain context data like DB connections
|
||||
|
@ -18,27 +18,27 @@ var spotifyClientID = os.Getenv("SPOTIFY_ID")
|
|||
var spotifyClientSecret = os.Getenv("SPOTIFY_TOKEN")
|
||||
|
||||
func setupRouter(env *Env, spotifyID string, spotifySecret string) *gin.Engine {
|
||||
var r *gin.Engine = gin.Default()
|
||||
var r *gin.Engine = gin.Default()
|
||||
r.Use(spotifyAuth(spotifyID, spotifySecret))
|
||||
r.GET("/alive", env.alive)
|
||||
r.GET("/artists/:artistID", env.getArtistByID)
|
||||
r.GET("/artists", env.getArtistByName)
|
||||
return r
|
||||
r.GET("/alive", env.alive)
|
||||
r.GET("/artists/:artistID", env.getArtistByID)
|
||||
r.GET("/artists", env.getArtistByName)
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
// If the auth/ID variables are empty something is probably misconfigured
|
||||
if spotifyClientID == "" {
|
||||
slog.Warn("[GOMUSIC] No Spotify ID configured in 'SPOTIFY_ID' environment variable")
|
||||
}
|
||||
if spotifyClientSecret == "" {
|
||||
slog.Warn("[GOMUSIC] No Spotify secret configured in 'SPOTIFY_SECRET' environment variable")
|
||||
}
|
||||
// If the auth/ID variables are empty something is probably misconfigured
|
||||
if spotifyClientID == "" {
|
||||
slog.Warn("[GOMUSIC] No Spotify ID configured in 'SPOTIFY_ID' environment variable")
|
||||
}
|
||||
if spotifyClientSecret == "" {
|
||||
slog.Warn("[GOMUSIC] No Spotify secret configured in 'SPOTIFY_SECRET' environment variable")
|
||||
}
|
||||
|
||||
db := setupDatabase()
|
||||
env := &Env{db: db}
|
||||
|
||||
|
||||
// Router/middleware and server setup
|
||||
r := setupRouter(env, spotifyClientID, spotifyClientSecret)
|
||||
r.Run(":8000")
|
||||
r := setupRouter(env, spotifyClientID, spotifyClientSecret)
|
||||
r.Run(":8000")
|
||||
}
|
||||
|
|
50
routes.go
50
routes.go
|
@ -1,60 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm/clause"
|
||||
"net/http"
|
||||
"log/slog"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm/clause"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (env *Env) alive(c *gin.Context) {
|
||||
c.String(http.StatusOK, "yes!")
|
||||
c.String(http.StatusOK, "yes!")
|
||||
}
|
||||
|
||||
func (env *Env) getArtistByID(c *gin.Context) {
|
||||
artistID := c.Params.ByName("artistID")
|
||||
spotifyAuthToken := c.GetString("spotifyAuthToken")
|
||||
|
||||
if artistID == "" || spotifyAuthToken == "Bearer " {
|
||||
artistID := c.Params.ByName("artistID")
|
||||
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"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
spotifyResponse, err := getSpotifyArtistData(artistID, spotifyAuthToken)
|
||||
if err != nil {
|
||||
statusCode := err.(*ResponseError).StatusCode
|
||||
switch statusCode {
|
||||
case 404:
|
||||
c.JSON(http.StatusNotFound, gin.H{"Error": "This artist does not exist"})
|
||||
return
|
||||
case 400:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"Error": "Bad request"})
|
||||
return
|
||||
case 404:
|
||||
c.JSON(http.StatusNotFound, gin.H{"Error": "This artist does not exist"})
|
||||
return
|
||||
case 400:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"Error": "Bad request"})
|
||||
return
|
||||
}
|
||||
slog.Error("[GOMUSIC] Failed to request latest spotify data from API", "Error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request the latest data from spotify API"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Update DB here
|
||||
var genreList []Genre
|
||||
for _, val := range spotifyResponse.Genres {
|
||||
genreList = append(genreList, Genre {Name: val})
|
||||
genreList = append(genreList, Genre{Name: val})
|
||||
}
|
||||
|
||||
artistProfile := ArtistProfile{
|
||||
SpotifyID: spotifyResponse.ID,
|
||||
Name: spotifyResponse.Name,
|
||||
SpotifyID: spotifyResponse.ID,
|
||||
Name: spotifyResponse.Name,
|
||||
Popularity: spotifyResponse.Popularity,
|
||||
Genres: genreList,
|
||||
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"}},
|
||||
Columns: []clause.Column{{Name: "spotify_id"}},
|
||||
UpdateAll: true,
|
||||
}).Create(&artistProfile)
|
||||
|
||||
|
@ -62,8 +62,8 @@ 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)
|
||||
// Send back our response data
|
||||
c.JSON(http.StatusOK, spotifyResponse)
|
||||
}
|
||||
|
||||
func (env *Env) getArtistByName(c *gin.Context) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAliveRoute(t *testing.T) {
|
||||
|
@ -49,7 +49,7 @@ func TestGetArtistByIDRoute(t *testing.T) {
|
|||
if dbResult.Error != nil {
|
||||
assert.Fail(t, "Failed to retrieve new info from test database")
|
||||
}
|
||||
|
||||
|
||||
assert.Equal(t, "Pitbull", artist.Name)
|
||||
assert.Equal(t, "0TnOYISbd1XYRBk9myaseg", artist.SpotifyID)
|
||||
}
|
||||
|
|
54
utils.go
54
utils.go
|
@ -1,22 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SpotifyResponse struct {
|
||||
ID string
|
||||
Name string
|
||||
Popularity int
|
||||
Genres []string
|
||||
ID string
|
||||
Name string
|
||||
Popularity int
|
||||
Genres []string
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
StatusCode int
|
||||
Err error
|
||||
Err error
|
||||
}
|
||||
|
||||
func (r ResponseError) Error() string {
|
||||
|
@ -28,44 +28,44 @@ func (r ResponseError) Error() string {
|
|||
func getSpotifyArtistData(artistID string, spotifyAuthToken string) (SpotifyResponse, error) {
|
||||
|
||||
artistEndpoint := fmt.Sprintf("https://api.spotify.com/v1/artists/%s", artistID)
|
||||
req, err := http.NewRequest("GET", artistEndpoint, nil)
|
||||
if err != nil {
|
||||
req, err := http.NewRequest("GET", artistEndpoint, nil)
|
||||
if err != nil {
|
||||
respErr := &ResponseError{
|
||||
StatusCode: 500,
|
||||
Err: errors.New("Failed to build request"),
|
||||
Err: errors.New("Failed to build request"),
|
||||
}
|
||||
return SpotifyResponse{}, respErr
|
||||
}
|
||||
req.Header.Add("Authorization", spotifyAuthToken)
|
||||
}
|
||||
req.Header.Add("Authorization", spotifyAuthToken)
|
||||
|
||||
// Send off request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// Send off request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
respErr := &ResponseError{
|
||||
StatusCode: 500,
|
||||
Err: errors.New("Failed to send request to spotify"),
|
||||
Err: errors.New("Failed to send request to spotify"),
|
||||
}
|
||||
return SpotifyResponse{}, respErr
|
||||
} else if resp.StatusCode != 200 {
|
||||
respErr := &ResponseError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Err: errors.New("Failed to get artist data from spotify API"),
|
||||
Err: errors.New("Failed to get artist data from spotify API"),
|
||||
}
|
||||
return SpotifyResponse{}, respErr
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var spotifyResponse SpotifyResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&spotifyResponse)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
respErr := &ResponseError{
|
||||
StatusCode: 500,
|
||||
Err: errors.New("Failed to decode response body"),
|
||||
Err: errors.New("Failed to decode response body"),
|
||||
}
|
||||
return SpotifyResponse{}, respErr
|
||||
}
|
||||
|
||||
// Close this immediately since it's unused now
|
||||
resp.Body.Close()
|
||||
return SpotifyResponse{}, respErr
|
||||
}
|
||||
|
||||
// Close this immediately since it's unused now
|
||||
resp.Body.Close()
|
||||
|
||||
// After all the above checks we assume this response is populated
|
||||
// If errors arise we can do extra validation here
|
||||
|
|
Loading…
Add table
Reference in a new issue