diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala b/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala index 6c22960..601b469 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala @@ -27,16 +27,17 @@ trait Auth[F[_]] { */ def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie] - def deleteSession(sessionId: Long): F[Unit] + def deleteSession(sessionId: Long): F[ResponseCookie] } object Auth { type SessionsMap = Map[Long, (RoomID, PlayerID)] - class SimpleAuth[F[_]: Sync](sessions: Ref[F, SessionsMap]) extends Auth[F] { + // TODO make "remove session usable from authed route" + val authcookieName = "authcookie" - val authcookieName = "authcookie" + class SimpleAuth[F[_]: Sync](sessions: Ref[F, SessionsMap]) extends Auth[F] { override def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie] = { // TODO check for existing session for same room @@ -67,8 +68,16 @@ object Auth { } } - override def deleteSession(sessionId: Long): F[Unit] = { - sessions.update(_.removed(sessionId)) + override def deleteSession(sessionId: Long): F[ResponseCookie] = { + sessions + .update(_.removed(sessionId)) + .as( + ResponseCookie( + name = authcookieName, + content = "", + secure = true + ).clearCookie + ) } } diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala index 7ca722a..918861e 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala @@ -35,17 +35,23 @@ object MyHttpService { wsb.build(send, receive) } case GET -> Root / "vote" / vote as (playerId, roomId) => { - // TODO forward these to the service implementation - IO(println(s">> got $vote from $playerId in $roomId")) >> Ok() + IO(println(s">> got $vote from $playerId in $roomId")) >> + roomService.acceptVote(roomId, playerId, vote) >> Ok() } case GET -> Root / "end-voting" as (playerId, roomId) => { - IO(println(s">> got request to end voting from $playerId in $roomId")) >> Ok() + IO(println(s">> got request to end voting from $playerId in $roomId")) >> + roomService.endVoting(roomId, playerId) >> Ok() } case GET -> Root / "new-poll" as (playerId, roomId) => { - IO(println(s">> got request to start new voting from $playerId in $roomId")) >> Ok() + IO(println(s">> got request to start new voting from $playerId in $roomId")) >> + roomService.startNewPoll(roomId, playerId) >> Ok() } case GET -> Root / "logout" as (playerId, roomId) => { - IO(println(s">> got request to logout from $playerId in $roomId")) >> Ok() + for { + _ <- IO(println(s">> got request to logout from $playerId in $roomId")) + _ <- roomService.leaveRoom(roomId, playerId) + cookie = ResponseCookie(name = Auth.authcookieName, content = "").clearCookie + } yield (Response(Status.Ok).addCookie(cookie)) } } diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala index 1dd8f85..f688a43 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala @@ -16,13 +16,17 @@ enum RoomError { trait RoomService[F[_]] { def createRoom(newRoom: Room): F[Either[RoomError, Room]] - def updateRoom(room: Room): F[Unit] + def updateRoom(roomId: RoomID, roomUpd: Room => Room): F[Unit] def joinRoom( id: RoomID, nickName: String, nickPassword: String, roomPassword: String ): F[Either[RoomError, PlayerID]] + def acceptVote(roomID: RoomID, playerID: PlayerID, vote: String): F[Unit] + def endVoting(roomID: RoomID, playerID: PlayerID): F[Unit] + def startNewPoll(roomID: RoomID, playerID: PlayerID): F[Unit] + def leaveRoom(roomID: RoomID, playerID: PlayerID): F[Unit] def deleteRoom(roomID: RoomID): F[Unit] def getRoom(roomID: RoomID): F[Option[Room]] def subscribe(roomID: RoomID): Stream[F, Room] @@ -44,20 +48,60 @@ class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, } } yield room } - override def updateRoom(room: Room): F[Unit] = { + override def updateRoom(roomId: RoomID, roomUpd: Room => Room): F[Unit] = { for { // modify is function to update state and compute auxillary value to return, here - topic - topic <- stateRef.modify[Topic[F, Room]] { state => - state.get(room.id) match { - case Some((oldRoom, topic)) => state.updated(room.id, (room, topic)) -> topic + publishUpd <- stateRef.modify[F[Unit]] { state => + state.get(roomId) match { + case Some((oldRoom, topic)) => + val newRoom = roomUpd(oldRoom) + state.updated(roomId, (newRoom, topic)) -> topic.publish1(newRoom).void case None => - throw new IllegalStateException(s"updateRoom with ${room.id} on nonexistent room") + throw new IllegalStateException(s"updateRoom with $roomId on nonexistent room") } } - _ <- topic.publish1(room) // update and publish are not atomic, sadly races can happen + _ <- + publishUpd // update and publish are not atomic, sadly races can happen (TODO use atomic ref) } yield () } + override def acceptVote(roomID: RoomID, playerID: PlayerID, vote: String): F[Unit] = + updateRoom( + roomID, + room => + room.round match { + case RoundState.Viewing(_) => room + case RoundState.Voting(votes) => + if (room.allowedCards.contains(vote)) + room.copy(round = RoundState.Voting(votes.updated(playerID, vote))) + else room + } + ) + // TODO check permission + override def endVoting(roomID: RoomID, playerID: PlayerID): F[Unit] = updateRoom( + roomID, + room => + room.round match { + case RoundState.Viewing(votes) => room.copy(round = RoundState.Viewing(votes)) + case RoundState.Voting(_) => room.copy(round = RoundState.Voting(Map.empty)) + } + ) + override def startNewPoll(roomID: RoomID, playerID: PlayerID): F[Unit] = updateRoom( + roomID, + room => + room.round match { + case RoundState.Viewing(_) => room.copy(round = RoundState.Voting(Map.empty)) + case RoundState.Voting(votes) => room.copy(round = RoundState.Viewing(votes)) + } + ) + + /** removes player from the active players keeps information on nick password, if one was present + */ + override def leaveRoom(roomID: RoomID, playerID: PlayerID): F[Unit] = updateRoom( + roomID, + room => room.copy(players = room.players.filterNot(_.id == playerID)) + ) + override def deleteRoom(roomID: RoomID): F[Unit] = { for { topic <- stateRef.modify[Topic[F, Room]](state =>