diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala index 918861e..c6f3cc6 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala @@ -22,9 +22,12 @@ object MyHttpService { val authedRoomRoutes: AuthedRoutes[(PlayerID, RoomID), IO] = AuthedRoutes.of { case GET -> Root / "subscribe" as (playerId, roomId) => { + val initial = Stream.evals(roomService.getRoom(roomId)) + val subscription = roomService.subscribe(roomId) + val send: Stream[IO, WebSocketFrame] = - roomService - .subscribe(roomId) + (initial ++ subscription) + .evalTap(state => IO(println(s">> sending room state $state to $playerId"))) .map(state => WebSocketFrame.Text(state.getViewFor(playerId).asJson.noSpaces)) val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap { @@ -69,13 +72,32 @@ object MyHttpService { playerId <- EitherT(roomService.joinRoom(roomId, nickName, nickPassword, roomPassword)) authCookie <- EitherT.liftF(auth.joinRoom(roomId, playerId)) _ <- EitherT.liftF(IO(println(s"> logging in $nickName to $roomName"))) - resp <- EitherT.liftF(Ok().flatTap(resp => IO(resp.addCookie(authCookie)))) + resp = Response(Status.Ok).addCookie(authCookie) } yield resp val response = responseOrError.leftSemiflatMap(error => Forbidden(error.toString())).merge response } + case req @ POST -> Root / "create-room" => { + val responseOrError = for { + data <- EitherT.right(req.as[Requests.LogIn]) + Requests.LogIn(roomName, nickName, roomPassword, nickPassword) = data + room <- EitherT(roomService.createRoom(roomName, nickName, nickPassword, roomPassword)) + owner = room.players.head // TODO add check + authCookie <- EitherT.liftF(auth.joinRoom(room.id, owner.id)) + _ <- EitherT.liftF( + IO(println(s"> logging in $nickName to new room $room | $authCookie")) + ) + resp = Response(Status.Ok).addCookie(authCookie) + _ <- EitherT.liftF(IO(println(s"> about to reply $resp ${resp.cookies}"))) + } yield resp + + val response = responseOrError.leftSemiflatMap(error => Forbidden(error.toString())).merge + + response + } + } (authenticationRoute <+> authMiddleware(authedRoomRoutes)).orNotFound diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala index f688a43..5ce24dd 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/RoomService.scala @@ -15,7 +15,12 @@ enum RoomError { } trait RoomService[F[_]] { - def createRoom(newRoom: Room): F[Either[RoomError, Room]] + def createRoom( + roomName: String, + nickName: String, + nickPassword: String, + roomPassword: String + ): F[Either[RoomError, Room]] def updateRoom(roomId: RoomID, roomUpd: Room => Room): F[Unit] def joinRoom( id: RoomID, @@ -35,19 +40,37 @@ trait RoomService[F[_]] { class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, Topic[F, Room])]]) extends RoomService[F] { - override def createRoom(newRoom: Room): F[Either[RoomError, Room]] = { + // TODO accept allowed cards and separate request + override def createRoom( + roomName: String, + nickName: String, + nickPassword: String, + roomPassword: String + ): F[Either[RoomError, Room]] = { for { updatesTopic <- Topic[F, Room] room <- stateRef.modify { rooms => - rooms.get(newRoom.id) match { + val roomId = RoomID(roomName) + rooms.get(roomId) match { case Some(_) => - rooms -> RoomError.RoomAlreadyExists(newRoom.id.name).asLeft[Room] + rooms -> RoomError.RoomAlreadyExists(roomName).asLeft[Room] case None => - (rooms.updated(newRoom.id, (newRoom, updatesTopic))) -> newRoom.asRight[RoomError] + val ownerPlayer = Player.create(nickName) + val newRoom = Room( + roomId, + players = List(ownerPlayer), + owner = ownerPlayer.id, + password = roomPassword, + allowedCards = List("XS", "S", "M", "L", "XL"), // TODO accept from front + round = RoundState.Voting(Map.empty), + playersPasswords = Map(nickName -> nickPassword) + ) + rooms.updated(newRoom.id, (newRoom, updatesTopic)) -> newRoom.asRight[RoomError] } } } yield room } + 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 @@ -82,8 +105,8 @@ class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, 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)) + case RoundState.Viewing(_) => room + case RoundState.Voting(votes) => room.copy(round = RoundState.Viewing(votes)) } ) override def startNewPoll(roomID: RoomID, playerID: PlayerID): F[Unit] = updateRoom( @@ -91,7 +114,7 @@ class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, room => room.round match { case RoundState.Viewing(_) => room.copy(round = RoundState.Voting(Map.empty)) - case RoundState.Voting(votes) => room.copy(round = RoundState.Viewing(votes)) + case RoundState.Voting(votes) => room } ) diff --git a/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala b/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala index 95e518f..2b082fa 100644 --- a/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala +++ b/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala @@ -76,6 +76,32 @@ object JoinRoomComponent { } } --> responseReceived ) + val newRoomButton = button( + "Create new room", + onClick + .mapTo { + (roomNameVar.now(), roomPassVar.now(), nicknameVar.now(), nicknamePass.now()) + } + .flatMap { case (roomName, roomPass, nickname, nicknamePass) => + Fetch + .post( + "/api/create-room", + body = Requests.LogIn( + roomName, + nickname, + roomPass, + nicknamePass + ) + ) + .text.map { response => + if (response.ok) { + loggedIn.onNext(true) + response + } else response + } + } --> responseReceived + ) + div( className := "flex flex-col h-full justify-center", @@ -85,6 +111,7 @@ object JoinRoomComponent { nameInput(nicknameVar, "Enter your nickname:"), passInput(nicknamePass, "nickname pass:"), submitButton, + newRoomButton, div( div( code("received:")