diff --git a/backend/handlers/handle_spotify.go b/backend/handlers/handle_spotify.go index 8f9d97d..569003a 100644 --- a/backend/handlers/handle_spotify.go +++ b/backend/handlers/handle_spotify.go @@ -3,6 +3,7 @@ package handlers import ( "net/http" + "adam-french.co.uk/backend/services" "github.com/gin-gonic/gin" "github.com/zmb3/spotify/v2" ) @@ -17,7 +18,7 @@ func (store *Store) CompleteAuth(c *gin.Context) { return } - store.SpotifyToken = token + services.SaveSpotifyToken(services.SPOTIFY_TOKEN_JSON_PATH, token) client := spotify.New(store.SpotifyAuth.Client(c.Request.Context(), token)) diff --git a/backend/handlers/store.go b/backend/handlers/store.go index 68b9b95..95a9371 100644 --- a/backend/handlers/store.go +++ b/backend/handlers/store.go @@ -3,13 +3,11 @@ package handlers import ( "github.com/zmb3/spotify/v2" spotifyauth "github.com/zmb3/spotify/v2/auth" - "golang.org/x/oauth2" "gorm.io/gorm" ) type Store struct { DB *gorm.DB SpotifyAuth *spotifyauth.Authenticator - SpotifyToken *oauth2.Token SpotifyClient *spotify.Client } diff --git a/backend/services/spotify.go b/backend/services/spotify.go index b6cef1f..f2d202b 100644 --- a/backend/services/spotify.go +++ b/backend/services/spotify.go @@ -1,9 +1,15 @@ package services import ( + "context" + "encoding/json" "fmt" + "os" + "time" + "github.com/zmb3/spotify/v2" spotifyauth "github.com/zmb3/spotify/v2/auth" + "golang.org/x/oauth2" ) type SpotifyConfig struct { @@ -13,7 +19,58 @@ type SpotifyConfig struct { ClientSecret string } -func InitSpotifyAuth(config SpotifyConfig) *spotifyauth.Authenticator { +const SPOTIFY_TOKEN_JSON_PATH = "spotify_token.json" + +func SaveSpotifyToken(path string, tok *oauth2.Token) error { + data := struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` + Expiry time.Time `json:"expiry"` + }{ + AccessToken: tok.AccessToken, + RefreshToken: tok.RefreshToken, + TokenType: tok.TokenType, + Expiry: tok.Expiry, + } + + jsonBytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + + // 0600 ensures only your user can read/write the file + return os.WriteFile(path, jsonBytes, 0600) +} + +func LoadSpotifyToken(path string) (*oauth2.Token, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var saved struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` + Expiry time.Time `json:"expiry"` + } + + if err := json.Unmarshal(data, &saved); err != nil { + return nil, err + } + + tok := &oauth2.Token{ + AccessToken: saved.AccessToken, + RefreshToken: saved.RefreshToken, + TokenType: saved.TokenType, + Expiry: saved.Expiry, + } + + return tok, nil +} + +func InitSpotifyAuth(config SpotifyConfig) (*spotifyauth.Authenticator, *spotify.Client) { auth := spotifyauth.New( spotifyauth.WithRedirectURL(config.RedirectURL), spotifyauth.WithClientID(config.ClientID), @@ -24,8 +81,34 @@ func InitSpotifyAuth(config SpotifyConfig) *spotifyauth.Authenticator { ), ) - fmt.Println("Authenticate spotify with:") - fmt.Println(auth.AuthURL(config.AuthState)) + // check if token exists locally + token, err := LoadSpotifyToken(SPOTIFY_TOKEN_JSON_PATH) + if err != nil || token == nil { + fmt.Println("No token saved. Authenticate Spotify with:") + fmt.Println(auth.AuthURL(config.AuthState)) + return auth, nil + } - return auth + // refresh token and client + client, err := RefreshClient(auth, token) + if err != nil { + fmt.Println("Failed to refresh token. Authenticate Spotify with:") + fmt.Println(auth.AuthURL(config.AuthState)) + return auth, nil + } + + return auth, client +} + +func RefreshClient(auth *spotifyauth.Authenticator, token *oauth2.Token) (*spotify.Client, error) { + ctx := context.Background() + + token, err := auth.RefreshToken(ctx, token) + if err != nil { + return nil, err + } + + client := spotify.New(auth.Client(ctx, token)) + + return client, nil }