Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/feelbeatserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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))
Expand Down
4 changes: 1 addition & 3 deletions cmd/feelbeatserver/setupapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
14 changes: 7 additions & 7 deletions internal/infra/api/roomapi/creategame.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(user auth.User, res http.ResponseWriter, req *http.Request) {
var payload room.RoomSettings
err := api.ParseBody(req.Body, &payload)
if err != nil {
Expand All @@ -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(user, payload)
if err != nil {
var fbError *feelbeaterror.FeelBeatError
if errors.As(err, &fbError) {
Expand All @@ -36,26 +36,26 @@ func (r RoomApi) createGameHandler(userId string, res http.ResponseWriter, req *
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
}

resJson, err := json.Marshal(createGameResponse{
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) {
Expand Down
56 changes: 56 additions & 0 deletions internal/infra/api/roomapi/fetchgames.go
Original file line number Diff line number Diff line change
@@ -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))
}
12 changes: 12 additions & 0 deletions internal/infra/api/setjsoncontenttype.go
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 6 additions & 4 deletions internal/infra/auth/authorizethroughspotify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(User, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(res http.ResponseWriter, req *http.Request) {
authHeader := req.Header.Get("Authorization")

Expand All @@ -21,13 +22,14 @@ func AuthorizeThroughSpotify(handler func(string, http.ResponseWriter, *http.Req
}
token := splits[1]

userId, err := spotify.GetUserId(token)
user, err := spotify.GetUserProfile(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(User{Profile: user, Token: token}, res, req)
}
}
13 changes: 11 additions & 2 deletions internal/infra/auth/authwrapper.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package auth

import "net/http"
import (
"net/http"

type AuthWrapper func(func(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)
4 changes: 3 additions & 1 deletion internal/lib/feelbeaterror/errorcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
32 changes: 29 additions & 3 deletions internal/lib/room/room.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
package room

import (
"github.com/feelbeatapp/feelbeatserver/internal/lib"
)

type Room struct {
id string
ownerId string
playlist lib.PlaylistData
owner lib.UserProfile
settings RoomSettings
players []Player
}

type Player struct {
}

func NewRoom(id string, ownerId string, settings RoomSettings) Room {
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,
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
}
33 changes: 24 additions & 9 deletions internal/lib/roomrepository/inmemoryroomrepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,47 @@ 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 {
FetchPlaylistData(playlistId string, token string) (lib.PlaylistData, 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) {
err := validation.ValidateRoomSettings(settings)
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)
newRoom := room.NewRoom(uuid.NewString(), playlistData, user.Profile, settings)
r.rooms[newRoom.Id()] = newRoom

fmt.Println(r.rooms)
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
}
8 changes: 6 additions & 2 deletions internal/lib/roomrepository/roomrepository.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package roomrepository

import "github.com/feelbeatapp/feelbeatserver/internal/lib/room"
import (
"github.com/feelbeatapp/feelbeatserver/internal/infra/auth"
"github.com/feelbeatapp/feelbeatserver/internal/lib/room"
)

type RoomRepository interface {
CreateRoom(string, room.RoomSettings) (string, error)
CreateRoom(user auth.User, settings room.RoomSettings) (string, error)
GetAllRooms() []room.Room
}
17 changes: 17 additions & 0 deletions internal/lib/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,20 @@ type SongDetails struct {
Artist string
Duration time.Duration
}

type Song struct {
Id string
Details SongDetails
}

type PlaylistData struct {
Name string
ImageUrl string
Songs []Song
}

type UserProfile struct {
Id string
Name string
ImageUrl string
}
8 changes: 0 additions & 8 deletions internal/lib/validation/roomsettings.go

This file was deleted.

Loading
Loading