diff --git a/internal/infra/ws/servewebsockets.go b/internal/infra/ws/servewebsockets.go index bb407c5..554f120 100644 --- a/internal/infra/ws/servewebsockets.go +++ b/internal/infra/ws/servewebsockets.go @@ -25,6 +25,13 @@ func (w WSHandler) websocketHandler(user auth.User, res http.ResponseWriter, req api.LogApiError("User tried to connect to non existing room", nil, user.Profile.Id, req) return } + + if len(room.PlayerProfiles()) >= room.Settings().MaxPlayers { + http.Error(res, feelbeaterror.RoomFull, feelbeaterror.StatusCode(feelbeaterror.RoomFull)) + api.LogApiError("user rejected, room full", nil, user.Profile.Id, req) + return + } + conn, err := upgrader.Upgrade(res, req, nil) if err != nil { http.Error(res, feelbeaterror.Default, feelbeaterror.StatusCode(feelbeaterror.Default)) diff --git a/internal/infra/ws/wshub.go b/internal/infra/ws/wshub.go index 8ae88a5..beb7006 100644 --- a/internal/infra/ws/wshub.go +++ b/internal/infra/ws/wshub.go @@ -67,6 +67,8 @@ func (h *WSHub) run() { close(h.register) close(h.unregister) close(h.rcv) + + fblog.Info(component.Hub, "hub closed") }() for { diff --git a/internal/lib/feelbeaterror/errorcode.go b/internal/lib/feelbeaterror/errorcode.go index 9a2edf6..d9ea3cd 100644 --- a/internal/lib/feelbeaterror/errorcode.go +++ b/internal/lib/feelbeaterror/errorcode.go @@ -9,6 +9,7 @@ const ( AuthFailed = "Authorization failed" LoadingPlaylistFailed = "Playlist loading failed" RoomNotFound = "Room not found" + RoomFull = "Room is full" EncodingMessageFailed = "Encoding message failed" ) @@ -16,6 +17,8 @@ func StatusCode(code ErrorCode) int { switch code { case RoomNotFound: return http.StatusNotFound + case RoomFull: + return http.StatusForbidden case AuthFailed: return http.StatusForbidden default: diff --git a/internal/lib/messages/server.go b/internal/lib/messages/server.go index a660654..f38ee8d 100644 --- a/internal/lib/messages/server.go +++ b/internal/lib/messages/server.go @@ -52,3 +52,8 @@ type SongState struct { ImageUrl string `json:"imageUrl"` DurationSec int `json:"durationSec"` } + +type PlayerLeftPayload struct { + Left string `json:"left"` + Admin string `json:"admin"` +} diff --git a/internal/lib/room/room.go b/internal/lib/room/room.go index 1cfafc9..5bdcc17 100644 --- a/internal/lib/room/room.go +++ b/internal/lib/room/room.go @@ -8,29 +8,31 @@ import ( ) type Room struct { - id string - playlist lib.PlaylistData - owner lib.UserProfile - settings lib.RoomSettings - players map[string]Player - hub messages.Hub - snd chan messages.ServerMessage - rcv <-chan messages.ClientMessage + id string + playlist lib.PlaylistData + owner lib.UserProfile + settings lib.RoomSettings + players map[string]Player + hub messages.Hub + snd chan messages.ServerMessage + rcv <-chan messages.ClientMessage + onCleanup func(*Room) } type Player struct { profile lib.UserProfile } -func NewRoom(id string, playlist lib.PlaylistData, owner lib.UserProfile, settings lib.RoomSettings, hub messages.Hub) *Room { +func NewRoom(id string, playlist lib.PlaylistData, owner lib.UserProfile, settings lib.RoomSettings, hub messages.Hub, onCleanup func(*Room)) *Room { return &Room{ - id: id, - playlist: playlist, - owner: owner, - settings: settings, - players: make(map[string]Player), - hub: hub, - snd: make(chan messages.ServerMessage), + id: id, + playlist: playlist, + owner: owner, + settings: settings, + players: make(map[string]Player), + hub: hub, + snd: make(chan messages.ServerMessage), + onCleanup: onCleanup, } } @@ -64,10 +66,6 @@ func (r *Room) Start() { go r.processMessages() } -func (r *Room) Stop() { - close(r.snd) -} - func (r *Room) Hub() messages.Hub { return r.hub } @@ -124,13 +122,26 @@ func (r *Room) removePlayer(id string) { for _, p := range r.players { recipents = append(recipents, p.profile.Id) } + if len(r.players) == 0 { + r.onCleanup(r) + r.cleanup() + return + } + + if id == r.owner.Id { + r.owner = r.players[recipents[0]].profile + fblog.Info(component.Room, "admin transfered", "roomId", r.id, "from", id, "to", r.owner.Id) + } fblog.Info(component.Room, "player leaves", "roomId", r.id, "playerId", id) r.snd <- messages.ServerMessage{ - To: recipents, - Type: messages.PlayerLeft, - Payload: id, + To: recipents, + Type: messages.PlayerLeft, + Payload: messages.PlayerLeftPayload{ + Left: id, + Admin: r.owner.Id, + }, } } @@ -148,3 +159,8 @@ func (r *Room) sendToAllExcept(id string, messageType messages.ServerMessageType Payload: payload, } } + +func (r *Room) cleanup() { + close(r.snd) + fblog.Info(component.Room, "room stopping", "id", r.id) +} diff --git a/internal/lib/roomrepository/inmemoryroomrepository.go b/internal/lib/roomrepository/inmemoryroomrepository.go index 6bba980..e2d5be9 100644 --- a/internal/lib/roomrepository/inmemoryroomrepository.go +++ b/internal/lib/roomrepository/inmemoryroomrepository.go @@ -1,6 +1,8 @@ package roomrepository import ( + "sync" + "github.com/feelbeatapp/feelbeatserver/internal/infra/auth" "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/feelbeatapp/feelbeatserver/internal/lib" @@ -18,24 +20,32 @@ type InMemoryRoomRepository struct { createHub func() messages.Hub spotify SpotifyApi rooms map[string]*room.Room + m sync.RWMutex } -func NewInMemoryRoomRepository(spotify SpotifyApi, createHub func() messages.Hub) InMemoryRoomRepository { - return InMemoryRoomRepository{ +func NewInMemoryRoomRepository(spotify SpotifyApi, createHub func() messages.Hub) *InMemoryRoomRepository { + return &InMemoryRoomRepository{ createHub: createHub, spotify: spotify, rooms: make(map[string]*room.Room), } } -func (r InMemoryRoomRepository) CreateRoom(user auth.User, settings lib.RoomSettings) (string, error) { +func (r *InMemoryRoomRepository) CreateRoom(user auth.User, settings lib.RoomSettings) (string, error) { playlistData, err := r.spotify.FetchPlaylistData(settings.PlaylistId, user.Token) if err != nil { return "", err } - newRoom := room.NewRoom(uuid.NewString(), playlistData, user.Profile, settings, r.createHub()) + newRoom := room.NewRoom(uuid.NewString(), playlistData, user.Profile, settings, r.createHub(), func(room *room.Room) { + r.m.Lock() + delete(r.rooms, room.Id()) + r.m.Unlock() + fblog.Info(component.RoomRepository, "removed room", "id", room.Id()) + }) + r.m.Lock() r.rooms[newRoom.Id()] = newRoom + r.m.Unlock() newRoom.Start() @@ -44,15 +54,19 @@ func (r InMemoryRoomRepository) CreateRoom(user auth.User, settings lib.RoomSett return newRoom.Id(), nil } -func (r InMemoryRoomRepository) GetAllRooms() []*room.Room { +func (r *InMemoryRoomRepository) GetAllRooms() []*room.Room { result := make([]*room.Room, 0) + r.m.RLock() for _, room := range r.rooms { result = append(result, room) } + r.m.RUnlock() return result } -func (r InMemoryRoomRepository) GetRoom(id string) *room.Room { +func (r *InMemoryRoomRepository) GetRoom(id string) *room.Room { + defer r.m.RUnlock() + r.m.RLock() return r.rooms[id] }