From 0b2a44b10e65b35ed834b58840a193a197017680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Mon, 6 Jan 2025 23:48:35 +0100 Subject: [PATCH 1/2] Implement songs fetching during room creation --- cmd/feelbeatserver/main.go | 3 +- internal/infra/api/roomapi/creategame.go | 4 +- .../infra/auth/authorizethroughspotify.go | 8 +- internal/infra/auth/authwrapper.go | 2 +- internal/lib/feelbeaterror/errorcode.go | 4 +- internal/lib/room/room.go | 6 +- .../roomrepository/inmemoryroomrepository.go | 26 ++++--- internal/lib/roomrepository/roomrepository.go | 6 +- internal/lib/types.go | 10 +++ internal/thirdparty/spotify/getuserprofile.go | 2 - internal/thirdparty/spotify/playlistsongs.go | 16 ++++ internal/thirdparty/spotify/spotifyapi.go | 76 +++++++++++++------ 12 files changed, 119 insertions(+), 44 deletions(-) create mode 100644 internal/thirdparty/spotify/playlistsongs.go diff --git a/cmd/feelbeatserver/main.go b/cmd/feelbeatserver/main.go index 0a6dc81..45c3d19 100644 --- a/cmd/feelbeatserver/main.go +++ b/cmd/feelbeatserver/main.go @@ -11,6 +11,7 @@ import ( "github.com/feelbeatapp/feelbeatserver/internal/infra/ws" "github.com/feelbeatapp/feelbeatserver/internal/lib/component" "github.com/feelbeatapp/feelbeatserver/internal/lib/roomrepository" + "github.com/feelbeatapp/feelbeatserver/internal/thirdparty/spotify" "github.com/knadh/koanf/v2" ) @@ -41,7 +42,7 @@ func main() { ws.ServeWebsockets(hub, w, r) }) - setupAPI(auth.AuthorizeThroughSpotify, roomrepository.NewInMemoryRoomRepository()) + setupAPI(auth.AuthorizeThroughSpotify, roomrepository.NewInMemoryRoomRepository(spotify.SpotifyApi{})) fblog.Info(component.FeelBeatServer, "Server started", "port", port) log.Fatal(http.ListenAndServe(fmt.Sprintf("localhost:%d", port), nil)) diff --git a/internal/infra/api/roomapi/creategame.go b/internal/infra/api/roomapi/creategame.go index ca97c86..63679fc 100644 --- a/internal/infra/api/roomapi/creategame.go +++ b/internal/infra/api/roomapi/creategame.go @@ -18,7 +18,7 @@ type createGameResponse struct { RoomId string `json:"roomId"` } -func (r RoomApi) createGameHandler(userId string, res http.ResponseWriter, req *http.Request) { +func (r RoomApi) createGameHandler(userId string, token string, res http.ResponseWriter, req *http.Request) { var payload room.RoomSettings err := api.ParseBody(req.Body, &payload) if err != nil { @@ -27,7 +27,7 @@ func (r RoomApi) createGameHandler(userId string, res http.ResponseWriter, req * return } - roomId, err := r.roomRepo.CreateRoom(userId, payload) + roomId, err := r.roomRepo.CreateRoom(userId, payload, token) if err != nil { var fbError *feelbeaterror.FeelBeatError if errors.As(err, &fbError) { diff --git a/internal/infra/auth/authorizethroughspotify.go b/internal/infra/auth/authorizethroughspotify.go index e1e0efe..1e1ad4d 100644 --- a/internal/infra/auth/authorizethroughspotify.go +++ b/internal/infra/auth/authorizethroughspotify.go @@ -6,10 +6,11 @@ import ( "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/lib/feelbeaterror" "github.com/feelbeatapp/feelbeatserver/internal/thirdparty/spotify" ) -func AuthorizeThroughSpotify(handler func(string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { +func AuthorizeThroughSpotify(handler func(string, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(res http.ResponseWriter, req *http.Request) { authHeader := req.Header.Get("Authorization") @@ -22,12 +23,13 @@ func AuthorizeThroughSpotify(handler func(string, http.ResponseWriter, *http.Req token := splits[1] userId, err := spotify.GetUserId(token) + if err != nil { - http.Error(res, http.StatusText(http.StatusForbidden), http.StatusForbidden) + http.Error(res, feelbeaterror.AuthFailed, http.StatusForbidden) fblog.Error(component.Auth, "Access denied", "reason", err) return } - handler(userId, res, req) + handler(userId, token, res, req) } } diff --git a/internal/infra/auth/authwrapper.go b/internal/infra/auth/authwrapper.go index 5f98e66..e3e8650 100644 --- a/internal/infra/auth/authwrapper.go +++ b/internal/infra/auth/authwrapper.go @@ -2,4 +2,4 @@ package auth import "net/http" -type AuthWrapper func(func(string, http.ResponseWriter, *http.Request)) func (http.ResponseWriter, *http.Request) +type AuthWrapper func(func(string, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) diff --git a/internal/lib/feelbeaterror/errorcode.go b/internal/lib/feelbeaterror/errorcode.go index fc470fb..4e4ac22 100644 --- a/internal/lib/feelbeaterror/errorcode.go +++ b/internal/lib/feelbeaterror/errorcode.go @@ -5,7 +5,9 @@ import "net/http" type ErrorCode string const ( - Default = "unexpected_error" + Default = "Unexpected error occurred" + AuthFailed = "Authorization failed" + LoadingPlaylistFailed = "Playlist loading failed" ) func StatusCode(code ErrorCode) int { diff --git a/internal/lib/room/room.go b/internal/lib/room/room.go index b5b3f51..cd0a781 100644 --- a/internal/lib/room/room.go +++ b/internal/lib/room/room.go @@ -1,16 +1,20 @@ package room +import "github.com/feelbeatapp/feelbeatserver/internal/lib" + type Room struct { id string ownerId string settings RoomSettings + songs []lib.Song } -func NewRoom(id string, ownerId string, settings RoomSettings) Room { +func NewRoom(id string, ownerId string, settings RoomSettings, songs []lib.Song) Room { return Room{ id: id, settings: settings, ownerId: ownerId, + songs: songs, } } diff --git a/internal/lib/roomrepository/inmemoryroomrepository.go b/internal/lib/roomrepository/inmemoryroomrepository.go index 30ed7e3..a0c038c 100644 --- a/internal/lib/roomrepository/inmemoryroomrepository.go +++ b/internal/lib/roomrepository/inmemoryroomrepository.go @@ -1,34 +1,42 @@ package roomrepository import ( - "fmt" - + "github.com/feelbeatapp/feelbeatserver/internal/lib" "github.com/feelbeatapp/feelbeatserver/internal/lib/room" "github.com/feelbeatapp/feelbeatserver/internal/lib/validation" "github.com/google/uuid" ) +type SpotifyApi interface { + FetchPlaylistSongs(playlistId string, token string) ([]lib.Song, error) +} + type InMemoryRoomRepository struct { - rooms map[string]room.Room + spotify SpotifyApi + rooms map[string]room.Room } -func NewInMemoryRoomRepository() InMemoryRoomRepository { +func NewInMemoryRoomRepository(spotify SpotifyApi) InMemoryRoomRepository { return InMemoryRoomRepository{ - rooms: make(map[string]room.Room), + spotify: spotify, + rooms: make(map[string]room.Room), } } // TODO: Implement fetching playlist details from spotify -func (r InMemoryRoomRepository) CreateRoom(ownderId string, settings room.RoomSettings) (string, error) { +func (r InMemoryRoomRepository) CreateRoom(ownderId string, settings room.RoomSettings, token string) (string, error) { err := validation.ValidateRoomSettings(settings) if err != nil { return "", err } - newRoom := room.NewRoom(uuid.NewString(), ownderId, settings) - r.rooms[newRoom.Id()] = newRoom + songs, err := r.spotify.FetchPlaylistSongs(settings.PlaylistId, token) + if err != nil { + return "", err + } - fmt.Println(r.rooms) + newRoom := room.NewRoom(uuid.NewString(), ownderId, settings, songs) + r.rooms[newRoom.Id()] = newRoom return newRoom.Id(), nil } diff --git a/internal/lib/roomrepository/roomrepository.go b/internal/lib/roomrepository/roomrepository.go index 23bd8a9..ab5b8e8 100644 --- a/internal/lib/roomrepository/roomrepository.go +++ b/internal/lib/roomrepository/roomrepository.go @@ -1,7 +1,9 @@ package roomrepository -import "github.com/feelbeatapp/feelbeatserver/internal/lib/room" +import ( + "github.com/feelbeatapp/feelbeatserver/internal/lib/room" +) type RoomRepository interface { - CreateRoom(string, room.RoomSettings) (string, error) + CreateRoom(playlistId string, settings room.RoomSettings, token string) (string, error) } diff --git a/internal/lib/types.go b/internal/lib/types.go index cbe7d65..af21645 100644 --- a/internal/lib/types.go +++ b/internal/lib/types.go @@ -7,3 +7,13 @@ type SongDetails struct { Artist string Duration time.Duration } + +type Song struct { + Id string + Details SongDetails +} + +type Playlist struct { + Id string + Songs []Song +} diff --git a/internal/thirdparty/spotify/getuserprofile.go b/internal/thirdparty/spotify/getuserprofile.go index 853968f..0be0d9f 100644 --- a/internal/thirdparty/spotify/getuserprofile.go +++ b/internal/thirdparty/spotify/getuserprofile.go @@ -8,8 +8,6 @@ import ( "github.com/feelbeatapp/feelbeatserver/internal/infra/api" ) -// TODO: Rethink error handling in GetUserId - func GetUserId(token string) (string, error) { req, err := newGetApiCall("/me", token) if err != nil { diff --git a/internal/thirdparty/spotify/playlistsongs.go b/internal/thirdparty/spotify/playlistsongs.go new file mode 100644 index 0000000..f79a26e --- /dev/null +++ b/internal/thirdparty/spotify/playlistsongs.go @@ -0,0 +1,16 @@ +package spotify + +type playlistSongsResponse struct { + Tracks struct { + Items []struct { + Track struct { + Artists []struct { + Name string `json:"name"` + } `json:"artists"` + Name string `json:"name"` + ID string `json:"id"` + DurationMs int `json:"duration_ms"` + } `json:"track"` + } `json:"items"` + } `json:"tracks"` +} diff --git a/internal/thirdparty/spotify/spotifyapi.go b/internal/thirdparty/spotify/spotifyapi.go index b594c62..10ac26d 100644 --- a/internal/thirdparty/spotify/spotifyapi.go +++ b/internal/thirdparty/spotify/spotifyapi.go @@ -1,47 +1,79 @@ package spotify import ( + "encoding/json" "fmt" - "io" - "log" "net/http" + "strings" "time" - "github.com/buger/jsonparser" + "github.com/feelbeatapp/feelbeatserver/internal/infra/api" "github.com/feelbeatapp/feelbeatserver/internal/lib" + "github.com/feelbeatapp/feelbeatserver/internal/lib/feelbeaterror" ) -func FetchSongDetails(spotifyId string, token string) { - url := fmt.Sprintf("https://api.spotify.com/v1/tracks/%s", spotifyId) - req, err := http.NewRequest("GET", url, nil) +type SpotifyApi struct { +} + +func (s SpotifyApi) FetchPlaylistSongs(plalistId string, token string) ([]lib.Song, error) { + url := fmt.Sprintf("/playlists/%s?additional_types=track&fields=tracks(items(track(id,images,name,artists(name),duration_ms)))", plalistId) + req, err := newGetApiCall(url, token) if err != nil { - return + return nil, &feelbeaterror.FeelBeatError{ + DebugMessage: err.Error(), + UserMessage: feelbeaterror.LoadingPlaylistFailed, + } } - req.Header.Set("Authorization", "Bearer "+token) res, err := http.DefaultClient.Do(req) if err != nil { - log.Fatal(err) + return nil, &feelbeaterror.FeelBeatError{ + DebugMessage: err.Error(), + UserMessage: feelbeaterror.LoadingPlaylistFailed, + } } - defer res.Body.Close() - bytes, err := io.ReadAll(res.Body) + bytes, err := api.ReadBody(res.Body) if err != nil { - log.Fatal(err) + return nil, &feelbeaterror.FeelBeatError{ + DebugMessage: err.Error(), + UserMessage: feelbeaterror.LoadingPlaylistFailed, + } } - - title, err := jsonparser.GetString(bytes, "name") + var songsResponse playlistSongsResponse + err = json.Unmarshal(bytes, &songsResponse) if err != nil { - log.Fatal(err) + return nil, &feelbeaterror.FeelBeatError{ + DebugMessage: err.Error(), + UserMessage: feelbeaterror.LoadingPlaylistFailed, + } + } + if len(songsResponse.Tracks.Items) == 0 { + return nil, &feelbeaterror.FeelBeatError{ + DebugMessage: "No songs in playlist", + UserMessage: feelbeaterror.LoadingPlaylistFailed, + } } - durationInMs, err := jsonparser.GetInt(bytes, "duration_ms") - if err != nil { - log.Fatal(err) + + songs := make([]lib.Song, 0, len(songsResponse.Tracks.Items)) + for _, item := range songsResponse.Tracks.Items { + artistNames := make([]string, 0, len(item.Track.Artists)) + for _, a := range item.Track.Artists { + artistNames = append(artistNames, a.Name) + } + + songs = append(songs, lib.Song{ + Id: item.Track.ID, + Details: lib.SongDetails{ + Title: item.Track.Name, + Artist: strings.Join(artistNames, " "), + Duration: time.Duration(item.Track.DurationMs) * time.Millisecond, + }, + }) } - fmt.Println(lib.SongDetails{ - Title: title, - Duration: time.Duration(durationInMs) * time.Millisecond, - }) + fmt.Println(songs) + + return songs, nil } From be85d23413bb012f4a39b2799d681e9d618ef033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Tue, 7 Jan 2025 19:39:06 +0100 Subject: [PATCH 2/2] Implement room fetching --- cmd/feelbeatserver/setupapi.go | 4 +- internal/infra/api/roomapi/creategame.go | 14 ++--- internal/infra/api/roomapi/fetchgames.go | 56 +++++++++++++++++++ internal/infra/api/setjsoncontenttype.go | 12 ++++ .../infra/auth/authorizethroughspotify.go | 6 +- internal/infra/auth/authwrapper.go | 13 ++++- internal/lib/room/room.go | 34 +++++++++-- .../roomrepository/inmemoryroomrepository.go | 29 ++++++---- internal/lib/roomrepository/roomrepository.go | 4 +- internal/lib/types.go | 13 ++++- internal/lib/validation/roomsettings.go | 8 --- internal/thirdparty/spotify/getuserprofile.go | 37 +++++++++--- ...aylistsongs.go => playlistdataresponse.go} | 6 +- internal/thirdparty/spotify/spotifyapi.go | 27 +++++---- 14 files changed, 199 insertions(+), 64 deletions(-) create mode 100644 internal/infra/api/roomapi/fetchgames.go create mode 100644 internal/infra/api/setjsoncontenttype.go delete mode 100644 internal/lib/validation/roomsettings.go rename internal/thirdparty/spotify/{playlistsongs.go => playlistdataresponse.go} (71%) diff --git a/cmd/feelbeatserver/setupapi.go b/cmd/feelbeatserver/setupapi.go index 4df1993..4305614 100644 --- a/cmd/feelbeatserver/setupapi.go +++ b/cmd/feelbeatserver/setupapi.go @@ -10,12 +10,10 @@ import ( const baseUrl = "/api/v1" -// TODO: Add contexts to api handler for graceful shutdown - func setupAPI(authWrapper auth.AuthWrapper, roomRepo roomrepository.RoomRepository) { roomApi := roomapi.NewRoomApi(roomRepo) - handlers := []func(string, auth.AuthWrapper){roomApi.ServeCreateGame} + handlers := []func(string, auth.AuthWrapper){roomApi.ServeCreateGame, roomApi.ServeFetchRooms} fblog.Info(component.Api, "Setting up REST API", "handlers count", len(handlers)) diff --git a/internal/infra/api/roomapi/creategame.go b/internal/infra/api/roomapi/creategame.go index 63679fc..cdf0c42 100644 --- a/internal/infra/api/roomapi/creategame.go +++ b/internal/infra/api/roomapi/creategame.go @@ -18,7 +18,7 @@ type createGameResponse struct { RoomId string `json:"roomId"` } -func (r RoomApi) createGameHandler(userId string, token string, res http.ResponseWriter, req *http.Request) { +func (r RoomApi) createGameHandler(user auth.User, res http.ResponseWriter, req *http.Request) { var payload room.RoomSettings err := api.ParseBody(req.Body, &payload) if err != nil { @@ -27,7 +27,7 @@ func (r RoomApi) createGameHandler(userId string, token string, res http.Respons return } - roomId, err := r.roomRepo.CreateRoom(userId, payload, token) + roomId, err := r.roomRepo.CreateRoom(user, payload) if err != nil { var fbError *feelbeaterror.FeelBeatError if errors.As(err, &fbError) { @@ -36,7 +36,7 @@ func (r RoomApi) createGameHandler(userId string, token string, res http.Respons http.Error(res, feelbeaterror.Default, feelbeaterror.StatusCode(feelbeaterror.Default)) } - api.LogApiError("Create room failed", err, userId, req) + api.LogApiError("Create room failed", err, user.Profile.Id, req) return } @@ -44,18 +44,18 @@ func (r RoomApi) createGameHandler(userId string, token string, res http.Respons RoomId: roomId, }) if err != nil { - api.LogApiError("Couldn't encode response", err, userId, req) + api.LogApiError("Couldn't encode response", err, user.Profile.Id, req) return } res.Header().Set("Content-Type", "application/json") - _, err = res.Write(resJson) + err = api.SendJsonResponse(&res, resJson) if err != nil { - api.LogApiError("Couldn't write response", err, userId, req) + api.LogApiError("Couldn't write response", err, user.Profile.Id, req) return } - api.LogApiCall(userId, req) + api.LogApiCall(user.Profile.Id, req) } func (r RoomApi) ServeCreateGame(baseUrl string, authWrapper auth.AuthWrapper) { diff --git a/internal/infra/api/roomapi/fetchgames.go b/internal/infra/api/roomapi/fetchgames.go new file mode 100644 index 0000000..88920ae --- /dev/null +++ b/internal/infra/api/roomapi/fetchgames.go @@ -0,0 +1,56 @@ +package roomapi + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/feelbeatapp/feelbeatserver/internal/infra/api" + "github.com/feelbeatapp/feelbeatserver/internal/infra/auth" +) + +type fetchGamesResponse struct { + Rooms []responseRoom `json:"rooms"` +} + +type responseRoom struct { + Id string `json:"id"` + Name string `json:"name"` + Players int `json:"players"` + MaxPlayers int `json:"maxPlayers"` + ImageUrl string `json:"imageUrl"` +} + +func (r RoomApi) fetchRoomsHandler(user auth.User, res http.ResponseWriter, req *http.Request) { + rooms := r.roomRepo.GetAllRooms() + formatted := fetchGamesResponse{ + Rooms: make([]responseRoom, 0), + } + + for _, room := range rooms { + formatted.Rooms = append(formatted.Rooms, responseRoom{ + Id: room.Id(), + Name: room.Name(), + Players: len(room.Players()), + MaxPlayers: room.Settings().MaxPlayers, + ImageUrl: room.ImageUrl(), + }) + } + + resJson, err := json.Marshal(formatted) + if err != nil { + api.LogApiError("Couldn't encode response", err, user.Profile.Id, req) + return + } + err = api.SendJsonResponse(&res, resJson) + if err != nil { + api.LogApiError("Couldn't write response", err, user.Profile.Id, req) + return + } + + api.LogApiCall(user.Profile.Id, req) +} + +func (r RoomApi) ServeFetchRooms(baseUrl string, authWrapper auth.AuthWrapper) { + http.HandleFunc(fmt.Sprintf("%s/rooms", baseUrl), authWrapper(r.fetchRoomsHandler)) +} diff --git a/internal/infra/api/setjsoncontenttype.go b/internal/infra/api/setjsoncontenttype.go new file mode 100644 index 0000000..961bbb7 --- /dev/null +++ b/internal/infra/api/setjsoncontenttype.go @@ -0,0 +1,12 @@ +package api + +import ( + "net/http" +) + +func SendJsonResponse(res *http.ResponseWriter, bytes []byte) error { + (*res).Header().Set("Content-Type", "application/json") + _, err := (*res).Write(bytes) + + return err +} diff --git a/internal/infra/auth/authorizethroughspotify.go b/internal/infra/auth/authorizethroughspotify.go index 1e1ad4d..c140a03 100644 --- a/internal/infra/auth/authorizethroughspotify.go +++ b/internal/infra/auth/authorizethroughspotify.go @@ -10,7 +10,7 @@ import ( "github.com/feelbeatapp/feelbeatserver/internal/thirdparty/spotify" ) -func AuthorizeThroughSpotify(handler func(string, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { +func AuthorizeThroughSpotify(handler func(User, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(res http.ResponseWriter, req *http.Request) { authHeader := req.Header.Get("Authorization") @@ -22,7 +22,7 @@ func AuthorizeThroughSpotify(handler func(string, string, http.ResponseWriter, * } token := splits[1] - userId, err := spotify.GetUserId(token) + user, err := spotify.GetUserProfile(token) if err != nil { http.Error(res, feelbeaterror.AuthFailed, http.StatusForbidden) @@ -30,6 +30,6 @@ func AuthorizeThroughSpotify(handler func(string, string, http.ResponseWriter, * return } - handler(userId, token, res, req) + handler(User{Profile: user, Token: token}, res, req) } } diff --git a/internal/infra/auth/authwrapper.go b/internal/infra/auth/authwrapper.go index e3e8650..7f02881 100644 --- a/internal/infra/auth/authwrapper.go +++ b/internal/infra/auth/authwrapper.go @@ -1,5 +1,14 @@ package auth -import "net/http" +import ( + "net/http" -type AuthWrapper func(func(string, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) + "github.com/feelbeatapp/feelbeatserver/internal/lib" +) + +type User struct { + Profile lib.UserProfile + Token string +} + +type AuthWrapper func(func(User, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) diff --git a/internal/lib/room/room.go b/internal/lib/room/room.go index cd0a781..a441d60 100644 --- a/internal/lib/room/room.go +++ b/internal/lib/room/room.go @@ -1,23 +1,45 @@ package room -import "github.com/feelbeatapp/feelbeatserver/internal/lib" +import ( + "github.com/feelbeatapp/feelbeatserver/internal/lib" +) type Room struct { id string - ownerId string + playlist lib.PlaylistData + owner lib.UserProfile settings RoomSettings - songs []lib.Song + players []Player } -func NewRoom(id string, ownerId string, settings RoomSettings, songs []lib.Song) Room { +type Player struct { +} + +func NewRoom(id string, playlist lib.PlaylistData, owner lib.UserProfile, settings RoomSettings) Room { return Room{ id: id, + playlist: playlist, + owner: owner, settings: settings, - ownerId: ownerId, - songs: songs, + players: make([]Player, 0), } } func (r Room) Id() string { return r.id } + +func (r Room) Name() string { + return r.playlist.Name +} + +func (r Room) Players() []Player { + return r.players +} + +func (r Room) ImageUrl() string { + return r.playlist.ImageUrl +} +func (r Room) Settings() RoomSettings { + return r.settings +} diff --git a/internal/lib/roomrepository/inmemoryroomrepository.go b/internal/lib/roomrepository/inmemoryroomrepository.go index a0c038c..f81f1a9 100644 --- a/internal/lib/roomrepository/inmemoryroomrepository.go +++ b/internal/lib/roomrepository/inmemoryroomrepository.go @@ -1,14 +1,16 @@ package roomrepository import ( + "fmt" + + "github.com/feelbeatapp/feelbeatserver/internal/infra/auth" "github.com/feelbeatapp/feelbeatserver/internal/lib" "github.com/feelbeatapp/feelbeatserver/internal/lib/room" - "github.com/feelbeatapp/feelbeatserver/internal/lib/validation" "github.com/google/uuid" ) type SpotifyApi interface { - FetchPlaylistSongs(playlistId string, token string) ([]lib.Song, error) + FetchPlaylistData(playlistId string, token string) (lib.PlaylistData, error) } type InMemoryRoomRepository struct { @@ -23,20 +25,25 @@ func NewInMemoryRoomRepository(spotify SpotifyApi) InMemoryRoomRepository { } } -// TODO: Implement fetching playlist details from spotify -func (r InMemoryRoomRepository) CreateRoom(ownderId string, settings room.RoomSettings, token string) (string, error) { - err := validation.ValidateRoomSettings(settings) - if err != nil { - return "", err - } - - songs, err := r.spotify.FetchPlaylistSongs(settings.PlaylistId, token) +func (r InMemoryRoomRepository) CreateRoom(user auth.User, settings room.RoomSettings) (string, error) { + playlistData, err := r.spotify.FetchPlaylistData(settings.PlaylistId, user.Token) if err != nil { return "", err } - newRoom := room.NewRoom(uuid.NewString(), ownderId, settings, songs) + newRoom := room.NewRoom(uuid.NewString(), playlistData, user.Profile, settings) r.rooms[newRoom.Id()] = newRoom + fmt.Println(newRoom) + return newRoom.Id(), nil } + +func (r InMemoryRoomRepository) GetAllRooms() []room.Room { + result := make([]room.Room, 0) + for _, room := range r.rooms { + result = append(result, room) + } + + return result +} diff --git a/internal/lib/roomrepository/roomrepository.go b/internal/lib/roomrepository/roomrepository.go index ab5b8e8..a687603 100644 --- a/internal/lib/roomrepository/roomrepository.go +++ b/internal/lib/roomrepository/roomrepository.go @@ -1,9 +1,11 @@ package roomrepository import ( + "github.com/feelbeatapp/feelbeatserver/internal/infra/auth" "github.com/feelbeatapp/feelbeatserver/internal/lib/room" ) type RoomRepository interface { - CreateRoom(playlistId string, settings room.RoomSettings, token string) (string, error) + CreateRoom(user auth.User, settings room.RoomSettings) (string, error) + GetAllRooms() []room.Room } diff --git a/internal/lib/types.go b/internal/lib/types.go index af21645..48f5220 100644 --- a/internal/lib/types.go +++ b/internal/lib/types.go @@ -13,7 +13,14 @@ type Song struct { Details SongDetails } -type Playlist struct { - Id string - Songs []Song +type PlaylistData struct { + Name string + ImageUrl string + Songs []Song +} + +type UserProfile struct { + Id string + Name string + ImageUrl string } diff --git a/internal/lib/validation/roomsettings.go b/internal/lib/validation/roomsettings.go deleted file mode 100644 index 3bbcede..0000000 --- a/internal/lib/validation/roomsettings.go +++ /dev/null @@ -1,8 +0,0 @@ -package validation - -import "github.com/feelbeatapp/feelbeatserver/internal/lib/room" - -// TODO: Implement validation of room settings -func ValidateRoomSettings(settings room.RoomSettings) error { - return nil -} diff --git a/internal/thirdparty/spotify/getuserprofile.go b/internal/thirdparty/spotify/getuserprofile.go index 0be0d9f..95c85e9 100644 --- a/internal/thirdparty/spotify/getuserprofile.go +++ b/internal/thirdparty/spotify/getuserprofile.go @@ -1,37 +1,56 @@ package spotify import ( + "encoding/json" "fmt" "net/http" - "github.com/buger/jsonparser" "github.com/feelbeatapp/feelbeatserver/internal/infra/api" + "github.com/feelbeatapp/feelbeatserver/internal/lib" ) -func GetUserId(token string) (string, error) { +type profileResponse struct { + Id string `json:"id"` + Name string `json:"display_name"` + Images []struct { + Url string `json:"url"` + } `json:"images"` +} + +func GetUserProfile(token string) (lib.UserProfile, error) { req, err := newGetApiCall("/me", token) if err != nil { - return "", fmt.Errorf("Failed to create user id request: %w", err) + return lib.UserProfile{}, fmt.Errorf("Failed to create user id request: %w", err) } res, err := http.DefaultClient.Do(req) if err != nil { - return "", fmt.Errorf("User id request failed: %w", err) + return lib.UserProfile{}, fmt.Errorf("User id request failed: %w", err) } if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("Auth request failed: %s", res.Status) + return lib.UserProfile{}, fmt.Errorf("Auth request failed: %s", res.Status) } bytes, err := api.ReadBody(res.Body) if err != nil { - return "", fmt.Errorf("Couldn't read user id request body: %w", err) + return lib.UserProfile{}, fmt.Errorf("Couldn't read user id request body: %w", err) } - userid, err := jsonparser.GetString(bytes, "id") + var response profileResponse + err = json.Unmarshal(bytes, &response) if err != nil { - return "", fmt.Errorf("Failed to parse user profile: %w", err) + return lib.UserProfile{}, fmt.Errorf("Failed to parse user profile: %w", err) + } + + var imageUrl string + if imageUrl = ""; len(response.Images) > 0 { + imageUrl = response.Images[0].Url } - return userid, nil + return lib.UserProfile{ + Id: response.Id, + Name: response.Name, + ImageUrl: imageUrl, + }, nil } diff --git a/internal/thirdparty/spotify/playlistsongs.go b/internal/thirdparty/spotify/playlistdataresponse.go similarity index 71% rename from internal/thirdparty/spotify/playlistsongs.go rename to internal/thirdparty/spotify/playlistdataresponse.go index f79a26e..5ee84d7 100644 --- a/internal/thirdparty/spotify/playlistsongs.go +++ b/internal/thirdparty/spotify/playlistdataresponse.go @@ -1,6 +1,10 @@ package spotify -type playlistSongsResponse struct { +type playlistDataResponse struct { + Name string `json:"name"` + Images []struct { + Url string `json:"url"` + } `json:"images"` Tracks struct { Items []struct { Track struct { diff --git a/internal/thirdparty/spotify/spotifyapi.go b/internal/thirdparty/spotify/spotifyapi.go index 10ac26d..e5a2e9d 100644 --- a/internal/thirdparty/spotify/spotifyapi.go +++ b/internal/thirdparty/spotify/spotifyapi.go @@ -15,11 +15,11 @@ import ( type SpotifyApi struct { } -func (s SpotifyApi) FetchPlaylistSongs(plalistId string, token string) ([]lib.Song, error) { - url := fmt.Sprintf("/playlists/%s?additional_types=track&fields=tracks(items(track(id,images,name,artists(name),duration_ms)))", plalistId) +func (s SpotifyApi) FetchPlaylistData(plalistId string, token string) (lib.PlaylistData, error) { + url := fmt.Sprintf("/playlists/%s?additional_types=track&fields=name,images(url),tracks(items(track(id,images,name,artists(name),duration_ms)))", plalistId) req, err := newGetApiCall(url, token) if err != nil { - return nil, &feelbeaterror.FeelBeatError{ + return lib.PlaylistData{}, &feelbeaterror.FeelBeatError{ DebugMessage: err.Error(), UserMessage: feelbeaterror.LoadingPlaylistFailed, } @@ -27,7 +27,7 @@ func (s SpotifyApi) FetchPlaylistSongs(plalistId string, token string) ([]lib.So res, err := http.DefaultClient.Do(req) if err != nil { - return nil, &feelbeaterror.FeelBeatError{ + return lib.PlaylistData{}, &feelbeaterror.FeelBeatError{ DebugMessage: err.Error(), UserMessage: feelbeaterror.LoadingPlaylistFailed, } @@ -35,22 +35,22 @@ func (s SpotifyApi) FetchPlaylistSongs(plalistId string, token string) ([]lib.So bytes, err := api.ReadBody(res.Body) if err != nil { - return nil, &feelbeaterror.FeelBeatError{ + return lib.PlaylistData{}, &feelbeaterror.FeelBeatError{ DebugMessage: err.Error(), UserMessage: feelbeaterror.LoadingPlaylistFailed, } } - var songsResponse playlistSongsResponse + var songsResponse playlistDataResponse err = json.Unmarshal(bytes, &songsResponse) if err != nil { - return nil, &feelbeaterror.FeelBeatError{ + return lib.PlaylistData{}, &feelbeaterror.FeelBeatError{ DebugMessage: err.Error(), UserMessage: feelbeaterror.LoadingPlaylistFailed, } } if len(songsResponse.Tracks.Items) == 0 { - return nil, &feelbeaterror.FeelBeatError{ + return lib.PlaylistData{}, &feelbeaterror.FeelBeatError{ DebugMessage: "No songs in playlist", UserMessage: feelbeaterror.LoadingPlaylistFailed, } @@ -73,7 +73,14 @@ func (s SpotifyApi) FetchPlaylistSongs(plalistId string, token string) ([]lib.So }) } - fmt.Println(songs) + var imageUrl string + if imageUrl = ""; len(songsResponse.Images) > 0 { + imageUrl = songsResponse.Images[0].Url + } - return songs, nil + return lib.PlaylistData{ + Name: songsResponse.Name, + ImageUrl: imageUrl, + Songs: songs, + }, nil }