Skip to content

Commit

Permalink
Merge pull request #6 from karanbihani/Karan_dev
Browse files Browse the repository at this point in the history
feat: Spotify integration
  • Loading branch information
souvik03-136 authored Aug 10, 2024
2 parents 4327092 + 69f7946 commit 786fac5
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 16 deletions.
9 changes: 0 additions & 9 deletions .env.example

This file was deleted.

6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ db/migrations/new:
.PHONY: db/migrations/up
db/migrations/up:
@echo 'Running Up migrations...'
migrate -path=./internal/database/migrations -database postgres://postgres:root@localhost:5432/spotifycollab?sslmode=disable up

migrate -path=./internal/database/migrations -database postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable up


## db/migrations/down: apply all database down migrations
.PHONY: db/migrations/down
db/migrations/down:
@echo 'Running Down migrations...'
migrate -path=./internal/database/migrations -database postgres://postgres:root@localhost:5432/spotifycollab?sslmode=disable down
migrate -path=./internal/database/migrations -database postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable down


.PHONY: all build run test clean
Expand Down
27 changes: 22 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,53 @@ require (
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/air-verse/air v1.52.3 // indirect
github.com/bep/godartsass v1.2.0 // indirect
github.com/bep/godartsass/v2 v2.1.0 // indirect
github.com/bep/golibsass v1.1.1 // indirect
github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cli/safeexec v1.0.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.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.22.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gohugoio/hugo v0.131.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/hashstructure v1.1.0 // 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 v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tdewolff/parse/v2 v2.7.15 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
119 changes: 119 additions & 0 deletions go.sum

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions internal/controllers/v1/playlists/handlers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package playlists

import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"spotify-collab/internal/database"
"spotify-collab/internal/merrors"
Expand Down Expand Up @@ -206,3 +211,131 @@ func (p *PlaylistHandler) UpdateConfigurationReq(c *gin.Context) {
})

}

// currently running get id everytime can remove it later if when creating account we associate user directly with spotify id in db

func GetUserId(AccessToken string) (string, error) {

url := "https://api.spotify.com/v1/me"

spotifyReq, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return "", err
}

spotifyReq.Header.Set("Authorization", "Bearer "+AccessToken)
spotifyReq.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(spotifyReq)
if err != nil {
fmt.Println("Error making request:", err)
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("non-OK HTTP status: %d", resp.StatusCode)
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("error reading response body: %v", err)
}

var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", fmt.Errorf("error parsing JSON: %v", err)
}

userId, ok := result["id"].(string)
if !ok {
return "", fmt.Errorf("user ID not found in response")
}

return userId, nil
}

// Create playlist

func (p *PlaylistHandler) CreatePlaylistSpotify(c *gin.Context) {
req, err := validateCreatePlaylistSpotifyReq(c)
if err != nil {
merrors.Validation(c, err.Error())
return
}

if req.AccessToken == "" {
merrors.Validation(c, "Access token is required")
return
}

if req.PlaylistName == "" {
merrors.Validation(c, "Playlist Name is required")
}

uid, err := GetUserId(req.AccessToken)
if err != nil {
merrors.InternalServer(c, err.Error())
return
}

requestBody := CreatePlaylistSpotifyReqBody{
PlaylistName: req.PlaylistName,
IsPublic: req.IsPublic || true,
IsCollaborative: req.IsCollaborative || false,
Description: req.Description,
}

body, err := json.Marshal(requestBody)
if err != nil {
fmt.Println("Error marshaling request body:", err)
return
}

fmt.Println("Request Body:", string(body))

url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists", uid)

fmt.Println("URL:", url)

spotifyReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
fmt.Println("Error creating request:", err)
return
}

// Set headers
spotifyReq.Header.Set("Authorization", "Bearer "+req.AccessToken)
spotifyReq.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(spotifyReq)
if err != nil {
fmt.Println("Error making request:", err)
return
}
defer resp.Body.Close()

responseBody, _ := io.ReadAll(resp.Body)
fmt.Println("Response Status:", resp.Status)
fmt.Println("Response Body:", string(responseBody))

if resp.StatusCode == http.StatusOK {
var responseBodyMap map[string]interface{}
err := json.NewDecoder(bytes.NewBuffer(responseBody)).Decode(&responseBodyMap)
if err != nil {
fmt.Println("Error decoding response body:", err)
return
}

snapshotID := responseBodyMap["snapshot_id"].(string)
fmt.Println("Added!!\n", snapshotID)
} else if resp.StatusCode == http.StatusUnauthorized {
fmt.Println("Error: Unauthorized - Invalid or expired access token")
} else {
fmt.Println("Error:", resp.Status, string(responseBody))
}

}
18 changes: 18 additions & 0 deletions internal/controllers/v1/playlists/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ type UpdateConfigurationReq struct {
RequireApproval *bool `json:"require_approval"`
MaxSong *int32 `json:"max_songs"`
}

type CreatePlaylistSpotifyReq struct {
PlaylistName string `json:"playlist_name"`
IsPublic bool `json:"is_public"`
IsCollaborative bool `json:"is_collaborative"`
Description string `json:"description"`

AccessToken string `json:"access_token"`
// UserID string `json:"user_id"`
}

type CreatePlaylistSpotifyReqBody struct {

PlaylistName string `json:"name"`
IsPublic bool `json:"public"`
IsCollaborative bool `json:"collaborative"`
Description string `json:"description"`
}
6 changes: 6 additions & 0 deletions internal/controllers/v1/playlists/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ func validateUpdateConfigurationReq(c *gin.Context) (UpdateConfigurationReq, err
err := c.ShouldBindJSON(&req)
return req, err
}

func validateCreatePlaylistSpotifyReq(c *gin.Context) (CreatePlaylistSpotifyReq, error) {
var req CreatePlaylistSpotifyReq
err := c.ShouldBindJSON(&req)
return req, err
}
76 changes: 76 additions & 0 deletions internal/controllers/v1/songs/handlers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package songs

import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"spotify-collab/internal/database"
"spotify-collab/internal/merrors"
Expand Down Expand Up @@ -157,3 +161,75 @@ func (s *SongHandler) DeleteBlacklistSong(c *gin.Context) {
StatusCode: http.StatusOK,
})
}

func (s *SongHandler) AddSongToPlaylist(c *gin.Context) {
req, err := validateAddSongToPlaylist(c)
if err != nil {
merrors.Validation(c, err.Error())
return
}

if req.AccessToken == "" {
merrors.Validation(c, "Access token is required")
return
}

if req.PlaylistID == "" {
merrors.Validation(c, "Playlist ID is required")
return
}

if len(req.SongURIList) == 0 {
merrors.Validation(c, "At least one song URI is required")
return
}

requestBody := RequestBody{Uris: req.SongURIList}
body, err := json.Marshal(requestBody)
if err != nil {
fmt.Println("Error marshaling request body:", err)
return
}

fmt.Println("Request Body:", string(body))

url := fmt.Sprintf("https://api.spotify.com/v1/playlists/%s/tracks", req.PlaylistID)

spotifyReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
fmt.Println("Error creating request:", err)
return
}

// Set headers
spotifyReq.Header.Set("Authorization", "Bearer "+req.AccessToken)
spotifyReq.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(spotifyReq)
if err != nil {
fmt.Println("Error making request:", err)
return
}
defer resp.Body.Close()

responseBody, _ := io.ReadAll(resp.Body)
fmt.Println("Response Status:", resp.Status)
fmt.Println("Response Body:", string(responseBody))

if resp.StatusCode == http.StatusOK {
var responseBodyMap map[string]interface{}
err := json.NewDecoder(bytes.NewBuffer(responseBody)).Decode(&responseBodyMap)
if err != nil {
fmt.Println("Error decoding response body:", err)
return
}

snapshotID := responseBodyMap["snapshot_id"].(string)
fmt.Println("Added!!\n", snapshotID)
} else if resp.StatusCode == http.StatusUnauthorized {
fmt.Println("Error: Unauthorized - Invalid or expired access token")
} else {
fmt.Println("Error:", resp.Status, string(responseBody))
}
}
12 changes: 12 additions & 0 deletions internal/controllers/v1/songs/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ type BlacklistSongReq struct {
type GetAllSongsReq struct {
PlaylistUUID uuid.UUID `json:"playlist_uuid"`
}

type AddSongToPlaylistReq struct {
SongURIList []string `json:"uris"`

// temporary pass playlist id and access token
PlaylistID string `json:"playlist_id"`
AccessToken string `json:"access_token"`
}

type RequestBody struct {
Uris []string `json:"uris"`
}
9 changes: 9 additions & 0 deletions internal/controllers/v1/songs/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ func validateGetAllSongsReq(c *gin.Context) (GetAllSongsReq, error) {
err := c.ShouldBindJSON(req)
return req, err
}

func validateAddSongToPlaylist(c *gin.Context) (*AddSongToPlaylistReq, error) {
var req AddSongToPlaylistReq
if err := c.ShouldBindJSON(&req); err != nil {
return nil, err
}
return &req, nil
}

4 changes: 4 additions & 0 deletions internal/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func (s *Server) RegisterRoutes() http.Handler {
r.DELETE("/playlists/:id", s.playlistHandler.DeletePlaylist)

r.POST("/songs/new", s.songHandler.AddSongToEvent)

// Route needs to be changed
r.POST("/songs/add", s.songHandler.AddSongToPlaylist)
r.POST("/playlists/add", s.playlistHandler.CreatePlaylistSpotify)

return r
}
Expand Down

0 comments on commit 786fac5

Please sign in to comment.